diff --git a/NEWS b/NEWS index 9a01a48659..2f7e4650f4 100644 --- a/NEWS +++ b/NEWS @@ -1,764 +1,774 @@ digiKam 7.0.0-rc - Release date: 2020-05-31 ***************************************************************************************************** NEW FEATURES: FaceManagement: New Neural Network engine based on OpenCV Deep Learning module to detect and recognize faces. FaceManagement: Face Scan dialog contents is now simplified and embeded into left side-bar tab. SlideShow : Add new shuffle mode. HTMLGallery : Add new theme "Html5Responsive". General : Code compile with Qt5.15 framework. General : All bundles have switched to last Qt 5.14.2 LTS. Linux and MacOS use QtWebEngine instead QtWebKit. General : All bundles have switched to last KF5 5.70.0. Fix support for Gimp XCF files >= 2.10. General : New FlatPak bundle processed by the KDE continuous integration stream. +General : New Plugin ImageMosaicWall to create an image based on a bunch of other photos. General : Add Microsoft Visual C++ support and a Continuous Integration workflow to check code with this compiler. General : Table-view is now able to show digiKam Tag-Paths properties. Metadata : New option to write geolocation data in file metadata. General : Internal libheif updated to last stable 1.6.2 and internal libde265 updated to last stable 1.0.5. General : Internal libraw updated to 0.20-beta1 (https://www.libraw.org/news/libraw-0-20-beta1). New camera supported: Canon CR3, PowerShot G5 X Mark II, G7 X Mark III, SX70 HS, EOS R, EOS RP, EOS 90D, EOS 250D, EOS M6 Mark II, EOS M50, EOS M200, EOS 1DX Mark III (lossless files only) DJI Mavic Air, Air2, Osmo Action, FujiFilm Fujifilm compressed/16bit, GFX 100, X-A7, X-Pro3, X100V, X-T4, X-T200, GoPro Fusion, HERO5, HERO6, HERO7, HERO8, Hasselblad L1D-20c, X1D II 50C, Leica D-LUX7, Q-P, Q2, V-LUX5, C-Lux / CAM-DC25, SL2, M10 Monochrom, Nikon D780, Z50, P950, Olympus TG-6, E-M5 Mark III, E-PL10, E-M1 Mark III, Panasonic Panasonic 14-bit, DC-FZ1000 II, DC-G90, DC-S1, DC-S1R, DC-S1H, DC-TZ95, PhaseOne IQ4 150MP, Rapsberry Pi RAW+JPEG format, Ricoh GR III, Sony A7R IV, A9 II, ILCE-6100, ILCE-6600, RX0 II, RX100 VII, Zenit M, also multiple smartphones. ***************************************************************************************************** BUGFIXES: 001 ==> 384401 - Various recognition algorithm improvements for face detection. 002 ==> 413701 - digiKam 6.3.0 does not build against current plasma. 003 ==> 413748 - Broken theme on Windows 10 after Digikam update. 004 ==> 413738 - Video Preview changes VLC volume. 005 ==> 382311 - Photos in collapsed groups are incorrectly excluded if first photo in group does not match filter. 006 ==> 396337 - In tag filter view (or any other filitered view), grouped photos should not always have "group" thumbnail 007 ==> 413704 - Filters do not work on hidden grouped images. 007 ==> 413233 - Application crashes when click in Albums left panel. 008 ==> 413759 - Tarball is missing translations. 009 ==> 413879 - Buttons without theme colors. 010 ==> 392015 - Show "Unknown" faces in a more visible and preeminent place in the "People" list. 011 ==> 413837 - Reverse geolocation deletes previous tags from the picture. 012 ==> 376629 - Face tagging dropdown: "Add in persons" adds person not label. 013 ==> 413923 - The peoples list shows just one line of height (one name) when expanded for the first time. 014 ==> 413924 - The face rectangle behavior when show face tags is OFF. 015 ==> 341111 - MYSQL : deleting an image involves "too many" queries. 016 ==> 413938 - Metadata is not written to all pictures in a tag hierarchy. 017 ==> 413916 - Compile Error with lqr on neon. 018 ==> 413972 - align_image_stack and enfuse use only 1 CPU core if called by digiKam plugin, 4 CPU core on command line. 019 ==> 413981 - Refresh has no effect in Preview mode. 020 ==> 413985 - Can't move an album too far down - no treeview scrolling. 021 ==> 412678 - Renaming folder leads to an error. 022 ==> 303239 - GROUP : grouped images are found, but do not show in searches. 023 ==> 321339 - GROUP : suppress display of albums containing only grouped images. 024 ==> 289911 - 23HQ should be split from flickr uploader as a new export tool. 025 ==> 414112 - Rounding problems in Resize Image function. 026 ==> 414052 - Packaging error of digikam-6.4.0.tar.xz, archive file contains a huge number of unnecessary files. 027 ==> 404667 - Bug of proofing for illuminant A and color cast. 028 ==> 414247 - digikam: error while loading shared libraries: libcudart.so.8.0. 029 ==> 392758 - Slideshow with random pictures [patch]. 030 ==> 414300 - Buttons on either side completely unresponsive. 031 ==> 414320 - Performance regression when scanning directories with many sub-directories. 032 ==> 414284 - Improve Setup/Slideshow View Options Layout. 033 ==> 414420 - Date/time in sidecar files for Videos seems to be ignored by digikam. 034 ==> 414340 - RawTherapee plugin. 035 ==> 414473 - Importing problem with M2Ts video files. 036 ==> 414521 - File management is broken. 037 ==> 205406 - Add Javascript to scaling images in html gallery [patch]. 038 ==> 334680 - Allow to limit exported files by type-mime in html gallery. 039 ==> 092462 - Add a new option to limit amount of thumbnails per index page. 040 ==> 136389 - Add new option to show image date and time in html gallery. 041 ==> 305167 - Add back filenames in html gallery. 042 ==> 114216 - Add a new option to export and merge more than one album in html gallery. 043 ==> 414516 - digikam crash when double-clicking on Google Maps when editing geolocation. 044 ==> 414603 - Pictures sort by freehand - drag & drop. 045 ==> 414630 - Shortcut bug in caption (tag). 046 ==> 414484 - Cannot use Rawtherapee or Darktable for raw image import on Windows. 047 ==> 166577 - List of available cameras in search. 048 ==> 414637 - New option for search. 049 ==> 414401 - RawTherapee does not work as Raw Import Tool if both are AppImages. 050 ==> 412067 - I get a error message: "Error while opening the database". 051 ==> 414902 - After using the "healing clone" tool only preview is available from other tools. 052 ==> 402894 - Display of original image. 053 ==> 380345 - Batch converting RAW to JPEG saves as new version. 054 ==> 383716 - "Save Changes" button same as "Save As New Version" Button. 055 ==> 307374 - Wrong image count in album view with versioning. 056 ==> 412961 - All image versions remain visible. 057 ==> 399923 - Segmentation fault during face detection. 058 ==> 392651 - Digikam crashes when Face scanning reaches 29%. 059 ==> 397919 - Segmentation fault during maintenance. 060 ==> 365354 - MYSQL : Application crash on scanning for faces in large picture set. 061 ==> 391014 - Crashes on close. 062 ==> 387821 - Face Recognition and Finding crashs after reaching 50%. 063 ==> 414749 - Export google photos: error transmission date creation. 064 ==> 377127 - Wrong item count on years in Dates View. 065 ==> 350350 - Moving images notifies me moving finished when it did not finish yet. 066 ==> 342191 - digiKam keeps crashing after crash with face-detection and manual tagging at the same time. 067 ==> 309769 - Crash on startup: 'Program received signal SIGILL, Illegal instruction.' (libopencv_nonfree). 068 ==> 286418 - digikam crashed during face recognition. 069 ==> 402470 - Crashed While I Walked Away During Face Detection. 070 ==> 415046 - Facial Recognition crash during scan. 071 ==> 360477 - Crash when adding a new face tag. 072 ==> 351638 - digiKam crash after facemarking. 073 ==> 350599 - Crash after maintenance. 074 ==> 350549 - Massive memory usage when assigning a tag to a recognized face. 075 ==> 312243 - digiKam crash when detected face moved or resized. 076 ==> 329108 - Crash when running Scanning faces/Clear and rebuild all training data. 077 ==> 280958 - Removing non-faces crashes digiKam. 078 ==> 312289 - Face Scan Crash. 079 ==> 285444 - Crash when adding face tag. 080 ==> 328413 - Crash while tagging faces - no background detection in process. 081 ==> 303328 - digiKam crashes repeatedly while tagging faces. 082 ==> 307554 - digiKam crash when scanning faces. 083 ==> 301506 - Crash while Tagging and Grouping Faces. 084 ==> 309306 - Crash during face-recognition. 085 ==> 284137 - digiKam crashed while moving a face tag after rotating a picture. 086 ==> 299173 - Crash during face tagging. 087 ==> 328560 - Crash when attempting to exit digiKam after aborted face recognition. 088 ==> 323828 - Crash when assigning a name to a single unknown face. 089 ==> 344735 - Crash if I name a face. 090 ==> 275688 - Crash during face detection, whilst tagging. 091 ==> 322022 - Faces and crash. 092 ==> 326750 - digiKam sometimes crashes when tagging recogniced faces with names. 093 ==> 314877 - Face Scan Crash. 094 ==> 271791 - digiKam crashes when removing a "face image" from the current list when the list is not yet loaded completely. 095 ==> 270410 - digiKam crashes when confirming faces. 096 ==> 283197 - Crash during tagging faces. 097 ==> 326689 - digiKam crashes while tagging faces. 098 ==> 284398 - Crash while tagging persons. 099 ==> 334158 - digiKam crash while tagging faces. 100 ==> 321851 - Face scan crash but only on multithread. 101 ==> 308393 - Crash while scanning faces. 102 ==> 326570 - digiKam crashes while recognizing/tagging faces. 103 ==> 343014 - Crashed when select another tag (face tag management). 104 ==> 327699 - Crashing while face detection. 105 ==> 308645 - digiKam crashed when I clicked scan faces while another scan was already running. 106 ==> 287961 - Crash while face-tagging pictures and doing import. 107 ==> 317863 - digiKam crashes as detected faces are being assigned to people or being removed while face detection is running. 108 ==> 302354 - Crash finding again faces (probably a duplicate of 262596). 109 ==> 320861 - Crash when adding tag to photo of the face scan result. 110 ==> 326794 - Tagging faces crashed digiKam. 111 ==> 274850 - digiKam crash on face scanning. 112 ==> 283165 - Crash happened while running face recognition. 113 ==> 280521 - digiKam crashes when deselecting two pictures in in quick succession from the face view. 114 ==> 303304 - Crash during face detection. 115 ==> 329596 - digiKam crashes during face recognition. 116 ==> 298599 - digiKam crashed while scaning for faces in the background. 117 ==> 304360 - Crash during face tagging. 118 ==> 280620 - digiKam crashes when tagging multiple non-faces. 119 ==> 302437 - Crash when re-searching for faces. 120 ==> 274727 - Crash on scan of faces. 121 ==> 333582 - Scanning for faces crashes. 122 ==> 290826 - digiKam crashed while tagging faces. 123 ==> 289003 - Crash when trying to face-scan all images. 124 ==> 280618 - digiKam crashes when tagging multiple faces. 125 ==> 329651 - Face detection crashes when reaches 3 GB of Memory, the computer has 6GB and 2 are still free. 126 ==> 294452 - digiKam crash in "Scan collection for faces". 127 ==> 268102 - Crash when finding faces. 128 ==> 323823 - Crash while first face detection after update. 129 ==> 336236 - Crash while tagging faces on more than one image at same time. 130 ==> 279266 - digiKam crashes while trying to tag faces. 131 ==> 312442 - digiKam crashed when editing face tags. 132 ==> 275827 - digiKam crash whilst scanning for faces, starting to enter tag. 133 ==> 330828 - Crash while detecting faces. 134 ==> 324711 - Crash when scaning faces. 135 ==> 323654 - digiKam crashed while i was tagging faces. 136 ==> 283540 - digiKam crashes on entering people tages (face recognition). 137 ==> 277099 - digiKam crash when adding a tag to a face. 138 ==> 296281 - digiKam crashed while manually adding a face. 139 ==> 325526 - Crash while tagging faces. 140 ==> 275637 - Performing multiple scans and recognises causes crash. 141 ==> 289228 - Crash when detecting faces. 142 ==> 317413 - digiKam crashed while tagging faces. 143 ==> 334580 - Face detection crashes digiKam. 144 ==> 301781 - digiKam crashed while scanning photos for faces. 145 ==> 321273 - Crash during face scan. 146 ==> 296784 - digiKam crashes while tagging faces. 147 ==> 285517 - digiKam crashed when tagging faces. 148 ==> 290818 - digiKam crash during face scanning. 149 ==> 271375 - digiKam crashed when unchecking face. 150 ==> 317290 - Crashes during face scan. 151 ==> 308575 - digiKam crashes while scanning for faces. 152 ==> 301832 - digiKam crashes a while after starting face detection scan. 153 ==> 300357 - digiKam crashes on Face Recognition scan. 154 ==> 309142 - Face Crash. 155 ==> 279781 - digiKam crashes while using face detection tool. 156 ==> 275387 - digiKam crashes on face recognition. 157 ==> 311934 - digiKam was crashing when I was face-tagging photos. 158 ==> 323428 - Crash while tagging a lot of faces at the same time. 159 ==> 262873 - digiKam crashes on scanning for faces. 160 ==> 280520 - Crash while running a face detection, and marking some of the faces. 161 ==> 322187 - Crash while 'detect and recognize faces'. 162 ==> 334337 - Crash when tagging faces in RAW picture files. 163 ==> 301856 - digiKam crashes during face tagging. 164 ==> 275541 - digiKam crashed while doing face recognition. 165 ==> 324093 - digiKam crashed when I clicked the check mark to assign an existing name to several selected face images. No background tasks were running. 166 ==> 286071 - Crash while adding face tags. 167 ==> 280901 - Crash when rejecting faces in different albums. 168 ==> 290891 - digiKam Crash while Face Recognition. 169 ==> 293418 - Crashes when scanning for faces. 170 ==> 273161 - digiKam: malloc(): smallbin double linked list corrupted. 171 ==> 268046 - Switch foto with face recognition. 172 ==> 277163 - digikam valgrind issues during face recognition testing. 173 ==> 302359 - Crash creating a tag while scanning faces. 174 ==> 307110 - digiKam crashes when manually adding face tags, then moving to next image. 175 ==> 339263 - digiKam crash. 176 ==> 379470 - Crash when performing face detection or recognition on large collection. 177 ==> 284154 - Crash upon exit. 178 ==> 392142 - Use Tensorflow for face and object recognition. 179 ==> 303501 - Double free or corruption during face tagging. 180 ==> 314646 - digikam crash when (tagging) scanning collection for faces. 181 ==> 317450 - Attempting to name a person while face tagging is in progress. 182 ==> 318640 - Face detection crash. 183 ==> 325385 - Face scan and using all processor cores. 184 ==> 325712 - Face tagging has massive memory leak. 185 ==> 326323 - Running Face Scan. 186 ==> 329164 - Changing face tags in an image causes digiKam to eat up all the virtual memory. 187 ==> 331912 - Face detection and recognition dos not work, tags and factags counts for one person are different, memory goes up and up without any result. 188 ==> 334509 - Person Detection. 189 ==> 337936 - Assigning a new person. 190 ==> 342144 - Face detection. 191 ==> 344661 - Face recognition makes digikam fill all the available memory. 192 ==> 345395 - Face Management the Memory and Swap growed to max. 193 ==> 365669 - Face Recognition improvement suggestion. 194 ==> 376901 - Face Recognition Algorithm Improvements. 195 ==> 338072 - digiKam face detection. 196 ==> 387870 - undefined reference to typeinfo for cv::face::FaceRecognizer. 197 ==> 327197 - When add one face tag in mainview/albums/one pictures view digikam and/or opencv fullfill the memory. 198 ==> 353859 - FaceEngine multi threaded ? 199 ==> 342004 - FacesEngine header and pkgconfig files not installed. 200 ==> 406838 - digiKam crashes while labeling unconfirmed faces. 201 ==> 409437 - Crash when doing any face detection. 202 ==> 404853 - digiKam faces engine fails to compile on PowerPC. 203 ==> 405625 - digiKam faces engine fails to compile on PowerPC with AltiVec enabled. 204 ==> 194401 - Face detection / recognition for digiKam for tags. 205 ==> 339823 - Detect and recognice faces crashes. 206 ==> 351077 - Crash during facial detection and recognition. 207 ==> 375317 - MYSQL: digikam crashes during face recognition. 208 ==> 299066 - digiKam crashed while tagging a large amount of people. 209 ==> 268761 - Segfault when tagging people without existing tag in preview mode. 210 ==> 374165 - Tag Change in Menu Person search crashes. 211 ==> 347753 - Surface freezes after some faces tagged. 212 ==> 375945 - Face detection not scanning my images. 213 ==> 343314 - Mass face tagging pictures when writing the tags into the files often crashes at the end of the writing. 214 ==> 345909 - digiKam crashed when face taging multiple photos. 215 ==> 401306 - digiKam doesn't compile with Opencv 4. 216 ==> 376766 - Face detection is using all CPU cores regardless of the checked option. 217 ==> 402320 - Memory hole (>1.5GB resident after 2 minutes) when detecting faces. 218 ==> 389031 - Scanning collection for faces causes produces lots of OpenCV errors. 219 ==> 325331 - MySQL : when adding a new face, tag is created with a new _Digikam_root_ tag in database. 220 ==> 262577 - Scanning collection for new faces (skip already scanned images) does not do anything. 221 ==> 330143 - "Detect and recognize faces" does no detection. 222 ==> 392527 - "Add A Face Tag" Dialogue Is Too Small. 223 ==> 372761 - Face Tag selection needs extra confirm now. 224 ==> 375418 - Glitch in face selection in album view. 225 ==> 281792 - When tagging with 'Return', too many faces are tagged. 226 ==> 282592 - Rejecting multiple faces at a time doesn't work properly. 227 ==> 376681 - Region Coordinates Are Sometimes inf / Large Numbers. 228 ==> 380251 - "Show only face tags..." not working properly. 229 ==> 326538 - When a picture is in portrait, face thumbnails are not rotated. 230 ==> 279208 - After the upgrade to Plasma and digiKam Faces Are Not Detected Anymore. 231 ==> 316856 - I suggest to extend face recognition to text-recognition system. 232 ==> 381378 - Face rectangle from XMP sidecar drawn incorrectly for EXIF rotated images. 233 ==> 326035 - Stepping through previews with "show face tags" enabled does not always show the tags. 234 ==> 326033 - Adding or changing face tag in preview window causes all tags on that preview to "hide". 235 ==> 265022 - UI's handling of People tags is confusing. 236 ==> 262180 - The toggle button to show/hide faces is difficult to see. 237 ==> 316161 - Reuse face tags from another picture. 238 ==> 412999 - Some photos downloaded in previous version of digikam no more recognized as already downloaded. 239 ==> 334215 - Cannot open RAW files from Sony A7R. 240 ==> 340595 - Convertion Sony A7 raw file turns to red the result. 241 ==> 307313 - digiKam uses wrong darkness/saturation values. 242 ==> 315156 - CR2 files from Canon PowerShot G1 X the colours are messed up. 243 ==> 332126 - LibRaw_r_LIBRARIES CMake Error: The following variables are used in this project, but they are set to NOTFOUND. 244 ==> 352996 - digiKam crashes while scanning for new files in Collection. 245 ==> 362779 - Can't decode PEF image from Pentax K-1 (36mp). 246 ==> 364063 - Crash on startup (related to crashing on conversion to DNG?). 247 ==> 221984 - Choosing manual WB does not allow user to tweak the previously selected WB setting. 248 ==> 182611 - Segfault with Samsung S85 RAW images. 249 ==> 178760 - Conversion of Canon .cr2 files broken as used in Darkroom. 250 ==> 364230 - Crash on startup. 251 ==> 244142 - digiKam crash. 252 ==> 362418 - Crashes on launch. 253 ==> 253091 - digiKam will not launch. 254 ==> 338075 - Tagging RAW images for Canon EOS-1Ds corrupts them. 255 ==> 205006 - On first run, digikam crashes while creating the index. 256 ==> 409148 - Sony A7r3 ARW files are displayed blurred in preview. 257 ==> 388339 - Crash when importing Pentax DNG file. 258 ==> 329230 - Crash when tuning a raw image. 259 ==> 253877 - digiKam Crash when loading. 260 ==> 134700 - CR2 thumbnails are not rotated. 261 ==> 090875 - Preview of RAW-Files in album. 262 ==> 143681 - Can't edit/view NEF anymore. 263 ==> 341024 - digiKam crash in a dir with only Canon Raw and JPEG files. 264 ==> 132695 - CR2 RAW files do not open. 265 ==> 339924 - RAW preview using embedded JPG. 266 ==> 142057 - Speed to view raw pictures. 267 ==> 187015 - Libraw breaks RW2 file handling. 268 ==> 126151 - Raw (NEF) thumbnail not correctly showed. 269 ==> 143244 - Error processing RAW files. 270 ==> 235321 - Tried to open X3F file. Was showing up in preview but crashed while opening in editor. 271 ==> 306843 - Raw-pics rotating. 272 ==> 149328 - Let RawEngine use raw decoding options when generating thumbnails. 273 ==> 150872 - Crash during during RAW conversion. 274 ==> 182013 - Sensitivity is not shown for rw2 files. 275 ==> 157619 - Let digiKam use preview images from raw files for more speed. 276 ==> 220322 - Crash when reading nef data: Warning: Exif tag Exif.NikonPreview.JPEGInterchangeFormatLength not encoded. 277 ==> 165176 - Previews of raw pics are not created when "create all previews new" is selected. 278 ==> 149086 - RAF-picture finepix not show in Imageeditor (thumbnails are displayed). 279 ==> 123950 - Crashing on raw files (.nef). 280 ==> 320049 - RawEngine is crashing on decoding some sigma raw files. 281 ==> 277707 - Easy way to convert Raw, to get same image as seen in preview. 282 ==> 146738 - Fuji *.RAF files aren't recognized as RAW files. 283 ==> 331397 - Preview and thumbnails colors are wrong for cr2. 284 ==> 210659 - Inconsistent Save / Save As handling in image editor after raw import. 285 ==> 155950 - Raw file open with action causes two instances. 286 ==> 337601 - digiKam crashed on closing after interrupted raw processing. 287 ==> 219748 - Image editor doesn't import raw images twice. 288 ==> 272725 - Some PEF/jpeg files not recognized as raw/non raw 289 ==> 154922 - Start to use libopenraw instead of libraw. 290 ==> 359949 - Bad canon raw file makes digikam to crash at startup. 291 ==> 101281 - EXIF info from RAW images not woking properly. 292 ==> 342233 - Crash when decoding pentax k-r raw photo. 293 ==> 139550 - Autocorrect levels for raw photos (which are shown too dark). 294 ==> 099437 - Incorrect Libraw option. 295 ==> 155156 - Raw file conversion crash single/batch mode. 296 ==> 146259 - Raw Converter won't convert to 16bit PNG. 297 ==> 151523 - RAW converter crashes on startup. 298 ==> 242479 - Crashes on appling refocus tool to raw image. 299 ==> 361678 - Dng Converter crashes when opening a raw file (*.NEF in my case). 300 ==> 382576 - DNG Image Converter crashed trying to convert a RAW image. 301 ==> 326268 - SONY SLT A58. 302 ==> 140087 - Sony Alpha Super SteadyShot Meta Info. 303 ==> 282116 - DNG Converter fails to convert Nikon NEF to DNG. 304 ==> 270457 - Crash when opening a NIKON-jpg with 1.3 MB. 305 ==> 135011 - Sort images by EXIF date seems buggy for Nikon (or not just Nikon?). 306 ==> 141249 - CANON EOS 5D not support. 307 ==> 240750 - DNG Converter produces black files (Canon 5D mkII) 308 ==> 361660 - DNG Image Converter crashes when converting Canon cr2 file. 309 ==> 211908 - TIF (RAW from Phase One and old Canon, not TIFF/EP) opens as thumbnail. 310 ==> 388222 - high RAM memory consumption of digiKam. 311 ==> 338249 - digiKam uses all free memory and gets terminated. 312 ==> 131277 - Memory leak in image editor. 313 ==> 252443 - digiKam leaves a zombie after quit. doesn't free memory. 314 ==> 321784 - Recreating fingerprints leaks memory. 315 ==> 381877 - digiKam start allocating all memory when scans a new collection. 316 ==> 098227 - Huge memory leak when downloading from camera. 317 ==> 330227 - Image quality sorter leaks memory. 318 ==> 412893 - Application windows are incorrectly magnified in macOS. 319 ==> 413656 - Manage Tags window appears with messed up double size graphics. 320 ==> 092783 - Usabilitiy of RAW file support. 321 ==> 158911 - digiKam crashes before RAW convert. 322 ==> 362870 - DNG Convert crash on convert NEF file. 323 ==> 367859 - DNG Converter crashed after trying to nconvert CR2 file. 324 ==> 369289 - DNG converter crashes when converting Olympus (.ORF) files. 325 ==> 407203 - Crash on trying to convert a .NEF file. 326 ==> 370623 - digiKam converter to DNG report an error while to process NEF image. 327 ==> 354364 - Crash of DNG converter. 328 ==> 338842 - Moving DNG images to another album corrupts DNG properties. 329 ==> 401849 - Cannot Save Current Search. 330 ==> 372972 - Find duplicates "search in drop down" only shows 1 item. 331 ==> 351521 - digiKam crashes when searching by date. 332 ==> 335978 - digiKam crashes when using timeline. 333 ==> 335052 - Crash on looking for duplicate. 334 ==> 333952 - Crashed when trying to search for duplicates. 335 ==> 218022 - Albums are sorted alphabetically within the month. 336 ==> 326495 - Calendar: higher flexibility in the layout. 337 ==> 281848 - Search for images with no goelocation. 338 ==> 283045 - Crash when searching with timeline. 339 ==> 281895 - digiKam crashed the selection date in the calendar. 340 ==> 280760 - Find duplicates should handle raw files more intelligently. 341 ==> 182029 - Duplicate item count not changed after duplicates deleted. 342 ==> 182043 - Duplicates search result in an inconsistent/broken state. 343 ==> 182492 - No photos are displayed when selecting a timeframe in the timeline. 344 ==> 140732 - Show all pictures of year when selecting year in date-tree. 345 ==> 240738 - Batch deletion of duplicate or similar files. 346 ==> 241536 - Reproducible crash when bilding fingerprint. 347 ==> 242438 - Crashes while creating fingerprints. 348 ==> 246500 - Fingerprint. 349 ==> 246635 - Crash when rebuilding fingerprints. 350 ==> 253382 - digiKam crashs creating fingerprints. 351 ==> 265670 - digiKam crashes when generating thumbnails and fingerprints in parallel. 352 ==> 265245 - Crash upon selecting certain month in timeline. 353 ==> 261418 - Improved handling of duplicate images. 354 ==> 263002 - Crash during browsing timeline. 355 ==> 147981 - Ability to move images to different albums from seach results. 356 ==> 149025 - Do not sort images in Search by album. 357 ==> 147407 - Root folders/years should display all child images. 358 ==> 155286 - Crash during duplicate search. 359 ==> 169404 - Color selector in fuzzy search is black. 360 ==> 214665 - Crash when cleaning duplicated images. 361 ==> 243136 - Fingerprints. 362 ==> 247550 - digiKam does not quit gracefully. 363 ==> 406228 - Getting random unextpected 'Database is locked' events. 364 ==> 409884 - digiKam Crash. 365 ==> 413944 - digiKam crashes while album browsing. 366 ==> 134817 - Introduce symlinks for album collection. 367 ==> 149983 - Show recursively sub-album images. 268 ==> 226770 - digiKam edit photo shift+space. 369 ==> 237161 - digiKam crashes on album viewing. 370 ==> 240436 - digiKam crashes when selecting a photo in an album. 371 ==> 291514 - Suggestions for improving face recognition performance. 372 ==> 271679 - digiKam detects but does not recognize faces. 373 ==> 292248 - Recognize faces does nothing. 374 ==> 321297 - Name not set on face recognition run. 375 ==> 314744 - Face do not recognize. 376 ==> 402021 - Face recognition not working. 377 ==> 392518 - Face recognition using the deep learning algo, dont move faces to Unconfirmed. 378 ==> 391671 - Face recognition fails giving a lot of addition effort to correct the errors. 379 ==> 392010 - Face recognition assigns faces to people without confirmation, leading to false positives. 380 ==> 277620 - Face recognition should propose the tagged name as default in the face identification page. 381 ==> 414308 - On a person, be able to view only the news found. 382 ==> 404167 - Improve confirmation process. 383 ==> 247571 - Crash while browsing in album view. 384 ==> 263209 - New Album crash. 385 ==> 275684 - Crash during tagging. 386 ==> 297044 - digiKam crashes when moving images to a new Album. 387 ==> 305108 - Crash during import of images. 388 ==> 317440 - digiKam crashes after deleting a tag. 389 ==> 335708 - Crash on image import. 390 ==> 337839 - I experience many crashes, without precise action. 391 ==> 339720 - Very slow tagging when missing Plasma icons have been used for tags. 392 ==> 343026 - Crashes when downloading. 393 ==> 361084 - Crash while importing. 394 ==> 117561 - Should store hashes of files if file is moved. 395 ==> 186920 - Too many open files. 396 ==> 135051 - digiKam crashes when building database from existing directory. 397 ==> 135689 - New folders not visible. 398 ==> 150181 - digiKam wants to delete the whole database (24.000Images) at startup when USB-Disk with the Photos is not connected. 399 ==> 194630 - digiKam fails to start ? after Plasma update. 400 ==> 202956 - Crashing on start up. 401 ==> 204071 - Just started digiKam and it crashed. 402 ==> 218726 - digiKam always crashes at startup with segmentation fault. 403 ==> 218860 - digiKam crashes when starting from krunner. 404 ==> 220172 - digiKam crash on scanning directories [mem2chunk_check, free_check, QHashData::free_helper]. 405 ==> 242305 - digiKam Crash. 406 ==> 242818 - digiKam crash at startup. 407 ==> 246065 - digiKam crashes at startup. 408 ==> 246534 - digiKam crashes at every start. 409 ==> 250418 - digiKam crashes on startup. 410 ==> 261624 - Moving an album from one collection to another doesn't update the source collection. 411 ==> 338171 - When automatic recognition guesses the same name for multiple faces in one picture, assigning a different name deletes all the face boxes except one. 412 ==> 392016 - Confirmed and unconfirmed faces look the same in a person's face list. 413 ==> 286452 - No way to scan untagged photos for faces, or scan ALL folders. 414 ==> 411732 - UI for assigning people tag is very fragile. 415 ==> 365668 - Face tagger allows text input when hovered over, then does crazy stuff. 416 ==> 413923 - The peoples list shows just one line of height (one name) when expanded for the first time. 417 ==> 326034 - Allow to add "unknown" face tags in Preview mode. 418 ==> 388649 - Face tag rectangle cursor sometimes disappear. 419 ==> 415560 - No default selection for Scan Collection. 420 ==> 415460 - JFIF files have APP0 marker after SOI where there should be APP1. 421 ==> 415582 - People Unknown faces does not decrease to zero. 422 ==> 415599 - Add date to help DNN face recognition for baby/kid/adult distinction. 423 ==> 415602 - How to erase faces rectangles for an album. 424 ==> 316897 - Face Detection improvements by colors filtering and using EXIF orientation. 425 ==> 088895 - Stops after a few percent with: Unknown event: 3. 426 ==> 091548 - Find duplicate images claims no album is selected although one is 427 ==> 101958 - Can not select duplicate images by clicking on a line instead of the select box of 6 pix square. 428 ==> 107095 - Double image removal: Use trashcan. 429 ==> 113557 - digiKam crashes during "Find Duplicate Images" with SIGSEGV. 430 ==> 117578 - Bad UI fpr searching duplicate images. 431 ==> 181698 - Cannot delete fuzzy searches. 432 ==> 181720 - Can't rename fuzzy searches. 433 ==> 183008 - Fuzzy image search requires two clicks to navigate back. 434 ==> 199045 - Find duplicates in a directory/directories. 435 ==> 199732 - Deleting duplicated images crashes digiKam. 436 ==> 222273 - Run fingerprint scan on new images. 437 ==> 231047 - Crash when updating fingerprints on a large photo collection (100GB). 438 ==> 235763 - Reproducible crash, when attempting to rebuild all fingerprints. 439 ==> 167168 - Timeline view shows redundant extra sections. 440 ==> 168004 - Timeline gives zero height column for dates with a single entry. 441 ==> 279674 - Incorporate "event view". 442 ==> 261216 - Debugging doesn´t work with installed. 443 ==> 109022 - Iphoto like calendar search. 444 ==> 109703 - Updating of calendar view in Date panel. 445 ==> 109705 - When quit date panel with day selected no images in other panels. 446 ==> 130230 - Wrong date in header using date-view. 447 ==> 213619 - Add a new view of albums based on due date. 448 ==> 375306 - Add 'Go to album' in search results. 449 ==> 128101 - Crash when selecting "Comments&Tags" tab, after selecting "Search" tab, with no search results selected. 450 ==> 146091 - Displaying raw photos by Tag or Date takes very long to load. 451 ==> 091372 - Make searching for multiple tags possible. 452 ==> 098846 - Searching photos with no tag. 453 ==> 113806 - Is it possible to have the size of the quick search window saved? 454 ==> 114848 - Search dialog: images not rotated correctly. 455 ==> 115536 - Quick search dialog box: wrong attached tips. 456 ==> 133294 - Advanced search dialog - combo box with tag names (when tag is to be matched) doesn't stretch with the dialog. 457 ==> 149555 - Always present search box instead of search by right-clicking and selecting simple or advanced search. 458 ==> 185106 - Advanced search is not saved/restored correctly. 459 ==> 120922 - Clicking on Advanced search crashes application. 460 ==> 141035 - Advanced Search gives error when trying to search by rating. 461 ==> 147429 - "and not" option in search function. 462 ==> 415489 - Win32 7.0.0 Beta 1 Crash On Opening Preview. 463 ==> 407540 - Missing EXIF parameter in DNG when converting Panasonic RW2 images. 464 ==> 399159 - Lenses name lost on converting to DNG. 465 ==> 118396 - Ability to search through image comments. 466 ==> 139283 - IPTC Caption comment in search function 467 ==> 155735 - Make it possible to seach on IPTC-text. 468 ==> 095584 - No indication of the end of download from camera. 469 ==> 179712 - Open image in embeded editor lead to digikam crash. 470 ==> 297293 - Option to use libnotify for non-blocking errors/message. 471 ==> 316928 - Servicing: statut of each servicing option should be searchable. 472 ==> 375521 - Progress bars waste useful space. 473 ==> 218583 - Showfoto customizing toolbar freerotation. 474 ==> 228879 - Geolocation Editor: Missing progress bar. 475 ==> 272158 - More user feedback about background operations. 476 ==> 301064 - Async task UI persists on leaving geolocation UI. 477 ==> 164600 - No picture in view pane. 478 ==> 297295 - Zooming: full picture thumbnail to show position. 479 ==> 160894 - Add new picLens like 3D view mode. 480 ==> 325530 - Preview and Editor return "Failed to Load Image" error when viewing JPEG (only) files. 481 ==> 256309 - Wish to save settings in batch queue manager. 482 ==> 271198 - Make queues saveable and use them as single action. 483 ==> 287407 - Save tools with settings in BQM. 484 ==> 318771 - Saved process don't remember settings in BQM. 485 ==> 320358 - Files randomly fail to process. 486 ==> 342433 - Crashes after tagging a few batches of photos. 487 ==> 171073 - Statistic of database, all pictures? 488 ==> 119228 - Storage of images on removable media. 489 ==> 144724 - Freezing by using NAS. 490 ==> 189362 - After moving the collection on a new HD, Digikam doesn't find it. 491 ==> 249375 - Crash when device with open window is removed. 492 ==> 272918 - digiKam crash after scanning ssh network drive. 493 ==> 275559 - digiKam crashes while browsing over NFS. 494 ==> 415679 - Faces are not recognized. 495 ==> 220903 - Duplicate records in ImageComments table after migration. 496 ==> 267800 - Keywords from IPTC-Metadata are not imported. 497 ==> 280678 - MIGRATION : cannot migrate from sqlite database. 498 ==> 301075 - MySQL : external database empty after migration. 499 ==> 328729 - digiKam crash while switching MySQL to SQLite 500 ==> 329849 - MIGRATION : db conversion fails. 501 ==> 361809 - Migration failed from SQLite to MySQL. 502 ==> 415700 - "Welcome to" screen still mentions v5.0. 503 ==> 415566 - Use existing face rectangles to improve recognition. 504 ==> 415603 - Add "Face Recognition" optional item in toolbar. 505 ==> 415592 - When renaming a tag, if I say no to updating, clicking Save won't trigger update again. 506 ==> 415766 - HEIF thumbnails are not shown in Timeline view. 507 ==> 396734 - Error while executing DBAction [ "UpdateSchemaFromV7ToV9" ] Statement [ "DROP TRIGGER IF EXISTS delete_image;" ]. 508 ==> 268204 - MYSQL : file-names are case-INsensitive. 509 ==> 383927 - OpenSuse digikam.coredb: Core database: schema update to V 8 failed! 510 ==> 319420 - Failed to update the database schema from version 5 to version 6. 511 ==> 288599 - Schema update to V6 failed. 512 ==> 218571 - Schema update version 4 to 5 failed. 513 ==> 230606 - digiKam doesn’t display images imported from database. 514 ==> 288839 - Failed to update the database schema from version 5 to version 6. 515 ==> 392179 - Database Upgrade to v9 fails. 516 ==> 396765 - Schema update to V 9 failed. 517 ==> 190411 - Metadata are invisible on older pictures. 518 ==> 110066 - Md5 Checksums to identify pictures. 519 ==> 415767 - Thumbnails for Portrait oriented images aren't rotated properly. 520 ==> 415791 - TimeAdjust can not adjust time by 1 unit - only by 2. 521 ==> 415557 - Face detection - Advanced settings : restricted folder scope not taken into account. 522 ==> 415702 - Cannot abort or stop find Duplicate process. 523 ==> 415796 - Face thumbnail zoom level too wide. 524 ==> 366551 - Unclear icon-item overlay when associating people to faces. 525 ==> 415535 - Import from a local/remote folder. 526 ==> 415643 - digiKam crashes Face detection. 527 ==> 415685 - digiKam crashes when scanning faces. 528 ==> 415877 - "Reset" button in Tag Manager not always working to reset icon. 529 ==> 415561 - Face detection based on deep learning leads to no result under Windows. 530 ==> 415920 - The unconfirmed faces count in the left sidebar is missing. 531 ==> 415944 - 7.0.0beta2 removed all my Geolocation bookmarks. 532 ==> 414016 - Log file /var/log/syslog grows in size very fast. 533 ==> 414028 - digiKam spamming journal with details of almost everything it does... 534 ==> 415882 - digiKam 6.4 crashes on processing a large number of new HEIC/HEIF files. 535 ==> 414115 - G'mic plugin not available on Mac. 536 ==> 416018 - Face detection: specified list of albums not taken into account. 537 ==> 416028 - "Search in Tags" selection for Face detection/recognition unduly changes. 538 ==> 416120 - Error transfering. 539 ==> 392304 - digiKam crash on exit (accessing stale QScreen instance during global destruction). 540 ==> 397694 - Missing text on selected tabs from QTabWidget. 541 ==> 380969 - Copy and paste text to comment field. 542 ==> 402914 - Crash when selecting custom icon for tag. 543 ==> 406507 - digiKam crashes when trying to select custom icon in the tag manager a second time. 544 ==> 401912 - Crash on adding GPS coordinates to multiple images. 545 ==> 412679 - Scrolling to a illogical place in the main view. 546 ==> 397761 - Tags are not written. 547 ==> 416231 - Exif date not written to sidecar file when modified, nor is it updated in the metadata sidebar. 548 ==> 402620 - AppImage digiKam does not respect desktop default application setting. 549 ==> 385953 - Reduce installed footprint under Windows. 550 ==> 416289 - 7.0.0-beta2 crash when closing Geolocation Editor. 551 ==> 413855 - digiKam crashes when deleting a letter in the login window (QtWebkit + libicu). 552 ==> 411619 - Crash when tagging, switching to map view (QtWebKit + libicu). 553 ==> 416345 - Proper OpenCV minimum version must be specified. 554 ==> 416289 - 7.0.0-beta2 crash when closing Geolocation Editor. 555 ==> 406809 - digikam-6.2.0-git-20190421T121202-qtwebengine-x86-64.appimage crashes on startup. 556 ==> 411891 - digiKam crashes if compiled with QWebEngine. 557 ==> 409906 - Crash at Startup in geolocation about QtWebEngine. 558 ==> 416371 - 7.0.0-beta bug in Album tree view sorting order. 559 ==> 404901 - Google auth dialog => firefox google auth window not closed. 560 ==> 372340 - Tagged face areas on portait (vertical) oriented images are mispositioned. 561 ==> 415941 - Face identification in wrong place. 562 ==> 415550 - Faces setup by Picasa are not displayed correctly on rotated/portrait pictures. 563 ==> 413926 - Incompatibility between software, for face regions on auto-rotated portrait position photos. 564 ==> 406971 - Face frames are misaligned on Digikam when face framing was done on Picasa. 565 ==> 395243 - Incorrect face regions on vertical images. 566 ==> 378456 - When previewing faces the thumbnail preview is sometime sideways or not aligned to the face in question. 567 ==> 377628 - CR2 file face detection misplaced in portrait. 568 ==> 412920 - Image Editor displays incorrect coordinates for selections on scaled HiDPI screens. 569 ==> 407914 - Crash when tagging, finding duplicates, moving between albums. 570 ==> 392607 - Crash sometimes when applying Tags to images. 571 ==> 406612 - Pictures are not placed on map when the've got GPS metadata. 572 ==> 406611 - There is no GPS icon over the thumbnail. 573 ==> 406941 - Geo-filter on the right pane does not work. 574 ==> 413081 - German user interface? 575 ==> 398869 - Tabs text gone when digiKam window active. 576 ==> 204479 - Unable to use digiKam on Windows. 577 ==> 414176 - Recognition of people. 578 ==> 415521 - digiKam is crashing very oftenly while loading preview of an image on Windows 10. 579 ==> 412453 - Crash after adding a new album-network drive. 580 ==> 412950 - While processing new collection, any (mouse) action will result in crash of digiKam. 581 ==> 398674 - digiKam duplicates an album when the folder name has been renamed to lowercase or uppercase. 582 ==> 392109 - Renamed tags are not written to metadata. 583 ==> 391839 - Grouped pictures no longer appear in albums, tags, searches or timeline. 584 ==> 391544 - Metadata not written to image after renaming Tag. 585 ==> 326870 - Pop up window for file attributes with bad colors. 586 ==> 376640 - Changing tag name does not update sidecar metadata. 587 ==> 240224 - Foreground and background of the Settings menu item have the same color when the Dark Theme is selected. 588 ==> 290072 - XMP sidecars available for all file types user wants to. 589 ==> 403349 - digiKam window offset on high resolution display. 590 ==> 415353 - FEATURE REQUEST: hotkey-mode to assign photos to album categories, quickly. 591 ==> 414212 - Slow response after tagging. 592 ==> 386098 - Crash when adding pictures to a running digiKam. 593 ==> 416526 - Exporting no longer works at all on the 20/01 version. 594 ==> 416551 - Create an utility to allow to create external soft links for tagged files from database. 595 ==> 372230 - Searches tool : "Current Searches" virtual album appears more than one. 596 ==> 416492 - Panorama tool fails to make panoramas. 597 ==> 416756 - When searching for duplicates, only one photo is shown if the other one is in a group (and not the first of the group). 598 ==> 416759 - In duplicate tab, it should be possible to have reference image always at first/last. 599 ==> 416802 - Add confirmation dialog for "Delete all" action. 600 ==> 416755 - Tag with "." not processed correctly, likely to result in data loss. 601 ==> 416984 - Build failure with opencv4-4.2.0. 602 ==> 406066 - Cannot parse date string YYYY-MM-DDTHH:MM:SS.ssss and YYYY:MM:DD HH:MM:SS in EXIF and XMP. 603 ==> 417008 - Flickr export ERR_CERT_AUTHORITY_INVALID. 604 ==> 417100 - kbuildsycoca5 cannot be started under MacOS 10.11.6 when digiKam PKG is installed. 605 ==> 417136 - Export pictures by creating symlinks (e.g. for a best-of-selection on filesystem). 606 ==> 417178 - Export pictures by creating "relative" symlinks. 607 ==> 417221 - "Tags Filter" not honouring "AND" modifier. 608 ==> 417255 - People > Search In > Albums/Tags requires scrollbar. 609 ==> 417257 - When navigating various TreeViews with keyboard, selected item is not always in focus. 610 ==> 412539 - digiKam crash when starting. 611 ==> 417317 - digikam git master crash. 612 ==> 417322 - Unable to move album because tree view does not scroll. 613 ==> 417333 - Advanced slideshow is blocking the desktop. 614 ==> 417633 - The Google Photos transfer no longer works. 615 ==> 409324 - digiKam freezes when exiting image editor from edit on image from filtered view. 616 ==> 417696 - Using the button "Determine difference from clock photo" without any result. 617 ==> 386141 - Export to Google Photos not responding under Windows with QtWebKit. 618 ==> 417786 - Renaming files in batch processor fails. 619 ==> 318516 - SCAN : Improve digiKam loading time [patch]. 620 ==> 417708 - Video export => geolocation is not exported. 621 ==> 397819 - digiKam not showing date taken. 622 ==> 327466 - digiKam crashes after launching alternate image processing software. 623 ==> 332903 - Altering Geo-Data. 624 ==> 417887 - Need Windows admin rights in order to see selected file list in Geolocation editor. 625 ==> 417797 - Batch video timestamp changing fails. 626 ==> 418056 - Not possible to filter for "no labels". 627 ==> 418022 - Difficulty in organizing tags if tags not visible in tag manager window. 628 ==> 418061 - Position of faces copied when rotating an image. 629 ==> 417946 - Tool not implemented. 630 ==> 418199 - Would it be possible to display file sizes in Megabytes? 631 ==> 418200 - 'Use native dialogs from system' option seems to do nothing on Win 10. 632 ==> 418342 - Tag shortcuts does not support single key shortcut. 633 ==> 418213 - When I add a Person to tagged Faces and delete it, it already exist. No recreation possible. 634 ==> 418138 - Export to local storage not working on Windows machine. 635 ==> 418057 - Icons missing in xubuntu. 636 ==> 418446 - Move Filters menu from right to left side of main window. 637 ==> 418594 - Should warn the user immediately when database not writable. 638 ==> 418536 - Editing date used as picture-taking date. 639 ==> 229010 - No support to resync GPS data from digikams database into into jpg files. 640 ==> 418658 - GPS data not exported on metadata export to sidecar files. 641 ==> 381347 - Non expected geolocation error message. 642 ==> 418760 - Suggested people tag does not appear on Unconfirmed thumbnails until you click on the thumbnail. 643 ==> 418554 - In face tagging, when manually entering a name, all other suggestions will default to that name. 644 ==> 418786 - Metadata Editors Missing. 645 ==> 418641 - Saves Mysql password in plain text. 646 ==> 406586 - Item count in "People" is not updated until you click or hover a tag. 647 ==> 414055 - Port Slideshow tool to plugin architecture [patch]. 648 ==> 419119 - Download limited to 100 photos. 649 ==> 419171 - Tagging multiple images at once broken with commit "modified CoreDB to perform recordChangeSet operation on tags". 650 ==> 419409 - Exif dates shown are wrong in some images. 651 ==> 419410 - Fuji S3Pro Image Preview Rotation is wrong. 652 ==> 419294 - Open with does not work with collection on network shares (on Windows). 653 ==> 418120 - Uninstaller recursively deleted C:/ drive. 654 ==> 419510 - Digikam crashed on exit after importing collection. 655 ==> 380838 - Metadata only written to DNG when "write metadata to Raw" selected. 656 ==> 402938 - Rename album an within album property fails when a subalbum exists. 657 ==> 417737 - File operations locked under Windows. 658 ==> 419667 - Preview in file opener. 659 ==> 419744 - 7.0.0-beta3 Geolocation Editor does not save new location. 660 ==> 419793 - Presentation shuffle not working. 661 ==> 419904 - Editor plugin icons same size or plugin list. 662 ==> 405995 - Large blank spaces between icons in "Tools" panel. 663 ==> 419771 - A possible configuration for automatically scanning for new added pictures? 664 ==> 420023 - RAW-editor integration with RawTherapee. 665 ==> 322117 - Open directory in File Manager and Open directory In Terminal not working under Windows and OSX. 666 ==> 279372 - Show combined file sizes when selection is done into icon-view. 667 ==> 420252 - Thumbnails of MPO images appear corrupted. 668 ==> 420254 - Preview of MPO stereo files displays a black image. 669 ==> 419927 - Filter by favorites. 670 ==> 415771 - Segmentation fault (11) crash when loading corrupt file with ImageMagick codec. 671 ==> 420398 - Install icon elongated. 672 ==> 420430 - Inconsistent behavior of View tab of Metadata dialog in digiKam Configure menu. 673 ==> 272730 - Sidebar does not adjust scroll area size automatically. 674 ==> 420400 - Emptying Trash does not provide any feedback that it is active/running. 675 ==> 420634 - Face thumbnails not zoomed in and have wrong orientation on network shares. 676 ==> 309598 - "Recent Tags" menu show existing tags added and shouldn't be. 677 ==> 420724 - A space at the end of the name of folder causes problem in Windows Explorer. 678 ==> 418688 - Face tagging in Thumbnails View? 679 ==> 405980 - The area of a "face rectangle" in the picture is different than the face thumbnail. 680 ==> 402940 - No thumbnails with windows client using network share. 681 ==> 420727 - digiKam-Window and all Sub-Windows are enlarged too much. 682 ==> 420642 - Crash on face detection stop button. 683 ==> 420773 - New pictures copied with shell in albums are not picked up from digikam. 684 ==> 420661 - Wishlist: option to clear/delete specific date field in a picture selection. 685 ==> 420865 - "Labels" tab in left menu shows unexpected behavior. 686 ==> 328291 - BQM Metadata Tool : be able to customize tags or namespaces to drop. 687 ==> 419852 - Seg fault (double free or corruption) while detecting faces, 0-length JPG files. 688 ==> 420875 - Assigning Tag/Label makes thumbnail disappear. 689 ==> 420564 - Google SSO auth broken. 690 ==> 420986 - External picture rating can not be read. 691 ==> 420978 - Error on new install Failed to update the database schema from version 9 to version 10. 692 ==> 421188 - Buttons in left and right sidebars are not visible except the active one. 693 ==> 309465 - Failed to load image for XCF files. 694 ==> 186641 - digiKam: doesn't open XCF files with GIMP 695 ==> 224697 - Crash with gimp xcf files with metadata. 696 ==> 250003 - XCF support is needed. 697 ==> 339152 - Crash when adding XCF file to Digikam collection. 698 ==> 368444 - Not supporting XCF anymore. 699 ==> 412339 - Bug report "Gimp 2.10 XCF serious issue". 700 ==> 406461 - Can't update from V9 to V10. 701 ==> 421361 - Removing a keyboard shortcut crashes Digikam. 702 ==> 421387 - Camera Leica V-Lux (Typ 114) not supported. 703 ==> 421023 - Face tagging quirky in digiKam 7.0.0-beta3. 704 ==> 421560 - Undo not working in ImageEditor RC. 705 ==> 421559 - HEIC: Failed to open the data source: Too many open files (errno = 24). 706 ==> 421464 - XPkeywords not being updated. 707 ==> 421521 - Wrong EXIF photograph properties shown when there is no EXIF data at all. 708 ==> 421646 - Impossible to add new tag. 709 ==> 418806 - digiKam:colorlabel in xmp sidecar files not compatible with external programs. 710 ==> 421693 - digiKam only read xmp:Label during first import then prioritizes its digiKam:ColorLabel over the xmp:Label. 711 ==> 421712 - Sidebar text turns white or disappear. 712 ==> 421791 - Single tag present in Xmp.dc.subject in XMP sidecar when multiple tags present in Xmp.lr.hierachicalSubject and database. 713 ==> 419472 - Flatpak in distribute.kde.org is outdated. 714 ==> 404642 - digiKam flatpak: Please include mysql driver (for using an external mysql db). 715 ==> 421801 - Feature Request - Please Add "Open With Default Application" to "Thumbnail Click Action" in Conf > Views > Icons section. 716 ==> 388422 - DBVersion setting does not exist. 717 ==> 421808 - Ignored directory does not clean up database. 718 ==> 421853 - Single button to refresh collection. 719 ==> 421817 - digiKam fails to compile with Qt 5.15. 720 ==> 421941 - Slideshow option does not respect sort order across collections. 721 ==> 421943 - Wishlist: Search for items without a tag in Advanced search. 722 ==> 420819 - Error while opening database digikam will try to automatically reconnect to the database. 723 ==> 421210 - digiKam 5.9.0-1+b1 refuses to start since LMDE4 Debbie upgrade. 724 ==> 421945 - Existing face tags not rotated with image. 725 ==> 422015 - Canon THM files (and likely others) are ignored by Sidecar management unless named very specifically. -726 ==> +726 ==> 114223 - New tool to build photomosaic using ImageMosaicWall. +727 ==> 398288 - digiKam no write access to folder. +728 ==> 421478 - Files written outside proper directories. +729 ==> 422087 - Move to different album does not move the original picture. +730 ==> 343458 - A tool like pixelize (e.g. for my favourite images). +731 ==> 422105 - When exiting full screen view by ESC, picture skips back to first viewed after a sequence. +732 ==> 406583 - Cannot write special characters (è, í, ó) in Tag search box. +733 ==> 422137 - Settings/Slideshow menu item missing. +734 ==> 422279 - Repeatable crash when rotating. +735 ==> diff --git a/core/app/DigikamExportAPI.cmake b/core/app/DigikamExportAPI.cmake index bd358790b3..1c6456f7ba 100644 --- a/core/app/DigikamExportAPI.cmake +++ b/core/app/DigikamExportAPI.cmake @@ -1,126 +1,133 @@ # # Copyright (c) 2010-2020 by Gilles Caulier, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # digiKam exported API for external DPlugins based projects. # # Note: all headers must export only Qt5 dependencies. # No KF5 dependencies are permitted: all external DPlugin based projects do not depend explicitly to KF5 API. # External DPlugins demo code can be found in this project: https://github.com/cgilles/digikamplugins-demo # ----------------------------------------------------------------------------------- # Headers to install install(FILES # As part of DigikamCore ${CMAKE_BINARY_DIR}/core/app/utils/digikam_config.h ${CMAKE_BINARY_DIR}/core/app/utils/digikam_core_export.h ${CMAKE_BINARY_DIR}/core/app/utils/digikam_database_export.h ${CMAKE_BINARY_DIR}/core/app/utils/digikam_gui_export.h ${CMAKE_SOURCE_DIR}/core/app/utils/digikam_export.h ${CMAKE_SOURCE_DIR}/core/app/utils/digikam_globals.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/core/dplugin.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/core/dpluginaction.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/core/dpluginauthor.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/core/dplugineditor.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/core/dplugingeneric.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/core/dpluginrawimport.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/setup/dpluginloader.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/setup/dpluginaboutdlg.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/iface/dinfointerface.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/iface/dmetainfoiface.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/widgets/ditemslist.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/widgets/dplugindialog.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/widgets/dpreviewimage.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/widgets/dpreviewmanager.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/widgets/dsavesettingswidget.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/widgets/dwizarddlg.h ${CMAKE_SOURCE_DIR}/core/libs/dplugins/widgets/dwizardpage.h ${CMAKE_SOURCE_DIR}/core/utilities/geolocation/geoiface/items/gpsitemcontainer.h ${CMAKE_SOURCE_DIR}/core/utilities/geolocation/geoiface/correlator/gpsdatacontainer.h ${CMAKE_SOURCE_DIR}/core/utilities/geolocation/geoiface/core/geoifacetypes.h ${CMAKE_SOURCE_DIR}/core/utilities/geolocation/geoiface/core/geocoordinates.h ${CMAKE_SOURCE_DIR}/core/utilities/geolocation/geoiface/reversegeocoding/rginfo.h ${CMAKE_SOURCE_DIR}/core/utilities/imageeditor/core/iofilesettings.h ${CMAKE_SOURCE_DIR}/core/utilities/imageeditor/editor/imageiface.h ${CMAKE_SOURCE_DIR}/core/utilities/imageeditor/editor/editortool.h ${CMAKE_SOURCE_DIR}/core/utilities/imageeditor/widgets/imageregionwidget.h ${CMAKE_SOURCE_DIR}/core/utilities/imageeditor/widgets/previewtoolbar.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/dimg.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/color/dcolor.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/color/dcolorpixelaccess.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/color/dcolorcomposer.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/color/dcolorblend.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/loaders/dimgloaderobserver.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/history/historyimageid.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/history/filteraction.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/filters/dimgbuiltinfilter.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/filters/dimgthreadedfilter.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/filters/dimgthreadedanalyser.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/filters/icc/iccprofile.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/filters/icc/icctransform.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/filters/bcg/bcgcontainer.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/filters/wb/wbcontainer.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/filters/curves/curvescontainer.h ${CMAKE_SOURCE_DIR}/core/libs/dimg/filters/raw/drawdecoding.h ${CMAKE_SOURCE_DIR}/core/libs/metadataengine/engine/metaengine.h ${CMAKE_SOURCE_DIR}/core/libs/metadataengine/engine/metaengine_data.h ${CMAKE_SOURCE_DIR}/core/libs/metadataengine/containers/photoinfocontainer.h ${CMAKE_SOURCE_DIR}/core/libs/rawengine/drawdecoder.h ${CMAKE_SOURCE_DIR}/core/libs/rawengine/drawinfo.h ${CMAKE_SOURCE_DIR}/core/libs/rawengine/drawdecodersettings.h ${CMAKE_SOURCE_DIR}/core/libs/widgets/files/filesaveconflictbox.h + ${CMAKE_SOURCE_DIR}/core/libs/widgets/colors/dcolorselector.h ${CMAKE_SOURCE_DIR}/core/libs/widgets/graphicsview/imagezoomsettings.h ${CMAKE_SOURCE_DIR}/core/libs/widgets/graphicsview/previewlayout.h ${CMAKE_SOURCE_DIR}/core/libs/widgets/graphicsview/dimgpreviewitem.h ${CMAKE_SOURCE_DIR}/core/libs/widgets/graphicsview/graphicsdimgview.h ${CMAKE_SOURCE_DIR}/core/libs/widgets/graphicsview/graphicsdimgitem.h ${CMAKE_SOURCE_DIR}/core/libs/threadimageio/preview/previewsettings.h ${CMAKE_SOURCE_DIR}/core/libs/threadimageio/fileio/loadingdescription.h ${CMAKE_SOURCE_DIR}/core/libs/threadimageio/fileio/loadsavethread.h ${CMAKE_SOURCE_DIR}/core/libs/threadimageio/thumb/thumbnailinfo.h ${CMAKE_SOURCE_DIR}/core/libs/threadimageio/thumb/thumbnailloadthread.h ${CMAKE_SOURCE_DIR}/core/libs/threadimageio/engine/managedloadsavethread.h ${CMAKE_SOURCE_DIR}/core/libs/threads/dynamicthread.h ${CMAKE_SOURCE_DIR}/core/libs/dialogs/dmessagebox.h + ${CMAKE_SOURCE_DIR}/core/libs/dialogs/imagedialog.h ${CMAKE_SOURCE_DIR}/core/libs/progressmanager/dhistoryview.h # As part of DigikamDatabase ${CMAKE_SOURCE_DIR}/core/libs/database/item/containers/iteminfo.h ${CMAKE_SOURCE_DIR}/core/libs/database/item/containers/iteminfolist.h ${CMAKE_SOURCE_DIR}/core/libs/database/coredb/coredbfields.h ${CMAKE_SOURCE_DIR}/core/libs/database/coredb/coredbconstants.h ${CMAKE_SOURCE_DIR}/core/libs/database/coredb/coredbalbuminfo.h ${CMAKE_SOURCE_DIR}/core/libs/database/coredb/coredbaccess.h ${CMAKE_SOURCE_DIR}/core/libs/database/coredb/coredburl.h ${CMAKE_SOURCE_DIR}/core/libs/database/engine/dbengineparameters.h ${CMAKE_SOURCE_DIR}/core/libs/database/engine/dbengineaction.h ${CMAKE_SOURCE_DIR}/core/libs/database/engine/dbengineconfig.h ${CMAKE_SOURCE_DIR}/core/libs/database/engine/dbengineerrorhandler.h ${CMAKE_SOURCE_DIR}/core/libs/database/engine/dbengineconfigsettings.h # As part of DigikamGui ${CMAKE_SOURCE_DIR}/core/utilities/queuemanager/dplugins/dpluginbqm.h ${CMAKE_SOURCE_DIR}/core/utilities/queuemanager/manager/batchtool.h ${CMAKE_SOURCE_DIR}/core/utilities/queuemanager/manager/queuesettings.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/digikam ) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/templates/DigikamPluginConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/DigikamPluginConfig.cmake) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/DigikamPluginConfig.cmake + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/DigikamPlugin") diff --git a/core/app/main/org.kde.digikam.appdata.xml b/core/app/main/org.kde.digikam.appdata.xml index d6dc67b9a3..08542d52b6 100644 --- a/core/app/main/org.kde.digikam.appdata.xml +++ b/core/app/main/org.kde.digikam.appdata.xml @@ -1,543 +1,477 @@ org.kde.digikam.desktop CC0-1.0 GPL-2.0+ digiKam دِجي‌كام digiKam digiKam digiKam digiKam digiKam digiKam digiKam digiKam digiKam digiKam digiKam digiKam digiKam digiKam digiKam digiKam digiKam digiKam ਡਿਜਿਕੈਮ digiKam digiKam digiKam digiKam digiKam digiKam Digikam digiKam digiKam xxdigiKamxx digiKam digiKam Photo Management Program Programa per a la gestió de les fotografies Programa per a la gestió de les fotografies Program pro správu fotografií Fotoverwaltung Πρόγραμμα διαχείρισης φωτογραφιών Photo Management Program Programa de gestión de fotos Fotohalduse rakendus Valokuvahallintaohjelma Application de gestion de photos Xestor de álbums de fotos Program Pengelolaan Foto Programma di gestione delle foto 사진 관리 프로그램 Fotobeheerprogramma Fotohandsamingsprogram ਫੋਟੋ ਇੰਤਜ਼ਾਮ ਲਈ ਪਰੋਗਰਾਮ Zarządzanie zdjęciami Programa de Gestão de Fotografias Programa de gerenciamento de fotos Программа для управления фотографиями Program za upravljanje fotografij Fotohanteringsprogram Resim Düzenleme Programı Програма для керування фотографіями xxPhoto Management Programxx 照片管理程序 圖片管理程式

digiKam is an advanced open-source digital photo management application that runs on Linux, Windows, and MacOS. The application provides a comprehensive set of tools for importing, managing, editing, and sharing photos and raw files. You can use digiKam’s import capabilities to easily transfer photos, raw files, and videos directly from your camera and external storage devices (SD cards, USB disks, etc.). The application allows you to configure import settings and rules that process and organize imported items on-the-fly.

+

xxdigiKam is an advanced open-source digital photo management application that runs on Linux, Windows, and MacOS. The application provides a comprehensive set of tools for importing, managing, editing, and sharing photos and raw files. You can use digiKam’s import capabilities to easily transfer photos, raw files, and videos directly from your camera and external storage devices (SD cards, USB disks, etc.). The application allows you to configure import settings and rules that process and organize imported items on-the-fly.xx

digiKam organizes photos, raw files, and videos into albums. But the application also features powerful tagging tools that allow you to assign tags, ratings, and labels to photos and raw files. You can then use filtering functionality to quickly find items that match specific criteria.

+

xxdigiKam organizes photos, raw files, and videos into albums. But the application also features powerful tagging tools that allow you to assign tags, ratings, and labels to photos and raw files. You can then use filtering functionality to quickly find items that match specific criteria.xx

In addition to filtering functionality, digiKam features powerful searching capabilities that let you search the photo library by a wide range of criteria. You can search photos by tags, labels, rating, data, location, and even specific EXIF, IPTC, or XMP metadata. You can also combine several criteria for more advanced searches. digiKam rely on Exiv2 library to handle metadata tag contents from files to populate the photo library.

+

xxIn addition to filtering functionality, digiKam features powerful searching capabilities that let you search the photo library by a wide range of criteria. You can search photos by tags, labels, rating, data, location, and even specific EXIF, IPTC, or XMP metadata. You can also combine several criteria for more advanced searches. digiKam rely on Exiv2 library to handle metadata tag contents from files to populate the photo library.xx

digiKam can handle raw files, and the application uses the excellent LibRaw library for decoding raw files. The library is actively maintained and regularly updated to include support for the latest camera models. digiKam can also manage video files for cataloging purpose, and the application uses the couple FFmpeg and QtAv libraries to extract metadata and play media.

+

xxdigiKam can handle raw files, and the application uses the excellent LibRaw library for decoding raw files. The library is actively maintained and regularly updated to include support for the latest camera models. digiKam can also manage video files for cataloging purpose, and the application uses the couple FFmpeg and QtAv libraries to extract metadata and play media.xx

The application provides a comprehensive set of editing tools. This includes basic tools for adjusting colors, cropping, and sharpening as well as advanced tools for, curves adjustment, panorama stitching, and much more. A special tool based on Lensfun library permit to apply lens corrections automatically on images.

+

xxThe application provides a comprehensive set of editing tools. This includes basic tools for adjusting colors, cropping, and sharpening as well as advanced tools for, curves adjustment, panorama stitching, and much more. A special tool based on Lensfun library permit to apply lens corrections automatically on images.xx

Extended functionality in digiKam is implemented via a set of tools based of plugins mechanism (named DPlugins for digiKam Plugins). Plugins can be written to import and export contents to remote web-services, add new features to edit image, and batch process photo.

-

digiKam je napredna aplikacija za upravljanje digitalnim fotografijama, što čini uvoz i organizaciju digitalnih fotografija "klikom". Fotografije su organizovane u albumima koji mogu biti sortirani hronološki, po rasporedu foldera ili po korisničkim kolekcijama.

-

El digiKam és una avançada aplicació per a gestionar les fotografies digitals, la qual fa que la importació i organització de les fotografies digitals sigui una «tasca fàcil». Les fotografies s'organitzen en àlbums, els quals es poden ordenar cronològicament, per disposició de la carpeta o per col·leccions personalitzades.

-

El digiKam és una avançada aplicació per a gestionar les fotografies digitals, la qual fa que la importació i organització de les fotografies digitals siga una «tasca fàcil». Les fotografies s'organitzen en àlbums, els quals es poden ordenar cronològicament, per disposició de la carpeta o per col·leccions personalitzades.

-

digiKam je pokročilý správce fotografií, se kterým snadno zvládnete import a uspořádání digitálních fotografií. Obrázky jsou organizovány v albech, která je možné seřadit podle data, podle struktury složek, nebo podle uživatelských sbírek.

-

digiKam ist eine leistungsfähige Anwendung zur Verwaltung von Digitalbildern. Es stellt eine einfache Oberfläche zum Importieren und Organisieren von Digitalbildern zur Verfügung. Die Bilder werden in Alben organisiert, die chronologisch oder nach Ordnern oder als benutzerdefinierte Sammlungen sortiert werden können.

-

Το digiKam είναι μια προηγμένη εφαρμογή διαχείρισης φωτογραφιών, που κάνει πανεύκολη την εισαγωγή και οργάνωση φωτογραφιών. Οι φωτογραφίες οργανώνονται σε άλμπουμ με χρονολογική ταξινόμηση, ανάλογα με τη διάταξη του φακέλου ή τις προσαρμοσμένες συλλογές.

-

digiKam is an advanced digital photo management application, which makes importing and organizing digital photos a "snap". The photos are organized in albums which can be sorted chronologically, by folder layout or by custom collections.

-

digiKam es una aplicación avanzada de gestión de fotos digitales que facilita la importación y la organización de fotos digitales. Las fotos se organizan en álbumes, que se pueden ordenar cronológicamente, por esquemas de carpetas o por colecciones personalizadas.

-

digiKam on võimas digitaalfotode haldamise rakendus, mis muudab digitaalfotode importimise ja korraldamise imelihtsaks. Fotod paigutatakse albumitesse, mida saab sortida aja, kataloogide või kohandatud kogude järgi.

-

digiKam on edistynyt digikuvien hallintaohjelma, joka tekee digitaalikuvien tuonnista ja järjestelystä vaivatonta. Kuva järjestetään albumeiksi, jotka voi lajitella ajan tai kansioasettelun mukaan tai mukautetuiksi kokoelmiksi.

-

digiKam est une application de gestion de photos avancée, qui rend l'import et l'organisation des photos comme un jeu d'enfant. Les photos sont organisées dans des albums qui peuvent être triés de manière chronologique, selon l'organisation des dossiers, ou par collections personnalisées.

-

digiKam é unha aplicación de administración avanzada de fotografía dixital que facilita a importación e organización de fotografías dixitais. As fotos organízanse en álbums, que poden ordenarse de maneira cronolóxica, por distribución do cartafol ou por coleccións personalizadas.

-

digiKam adalah sebuah aplikasi pengelola foto digital dimana pengimporan dan pengorganisasian foto digital "snap". Foto yang diorganisir di album yang bisa disimpan secara kronologis, menurut tataletak folder atau berdasarkan koleksi kustom.

-

digiKam è un'applicazione per la gestione avanzata delle fotografie digitali, che permette di importare e organizzare le proprie foto in pochi clic. Le foto sono organizzate in album, i quali possono essere ordinati per data, per disposizione delle cartelle o per raccolte personalizzate.

-

digiKam은 디지털 사진을 가져오고 관리하는 고급 디지털 사진 관리 프로그램입니다. 사진은 시간순, 폴더별 등 다양한 방법으로 정리할 수 있습니다.

-

digiKam is een geavanceerde toepassing voor beheer van digitale foto's, die importeren en organiseren van digitale foto's een "fluitje van een cent" maakt. De foto's zijn georganiseerd in albums die chronologisch, op indeling van mappen of op eigen verzamelingen gesorteerd kunnen zijn.

-

digiKam er eit avansert fotohandsamingsprogram som gjer det kjapt og enkelt å importera og organisera digitale bilete. Bileta vert ordna i album som kan sorterast kronologisk, etter mappestruktur eller i eigendefinerte samlingar.

-

digiKam jest zaawansowanym programem do zarządzania cyfrowymi zdjęciami, który czyni importowanie i zarządzanie cyfrowymi zdjęciami "drobnostką". Zdjęcia są urządzone w albumy, które można przechowywać chronologicznie, według układu katalogów lub według własnych zbiorów.

-

O digiKam é uma aplicação de gestão de fotografias digitais, o que torna a importação e a organização de fotografias digitais extremamente simples. As fotos são organizadas em álbuns, os quais podem ser ordenados de forma cronológica, por disposição das pastas ou com colecções personalizadas.

-

DigiKam é um aplicativo para gerenciamento de fotos digitais avançado, que torna muito fácil a importação e organização das suas fotos digitais. As fotos são organizadas em álbuns e ordenadas de forma cronológica, por layout de pastas ou por coleções personalizadas.

-

digiKam — приложение для управления фотографиями с расширенным набором возможностей. Фотографии сохраняются в альбомах, что позволяет упорядочивать их в хронологическом порядке, по иерархии папок, а также в коллекции, определённые пользователем.

-

digiKam je pokročilá aplikácia na správu digitálnych fotografií, ktorá zjednodušuje import a správu digitálnych fotografií. Fotografie sú organizované v albumoch, ktoré sa dajú triediť chronologicky, podľa rozloženia priečinkov alebo podľa vlastných zbierok.

-

digiKam je napreden program za upravljanje digitalnih fotografij, ki močno poenostavi uvažanje in organiziranje digitalnih fotografij. Fotografije so organizirane v albumih, ki jih lahko razvrstite po času, razporedu map ali zbirkah po meri.

-

Digikam är ett avancerat program för hantering av digitala foton, som gör det enkelt att importera och organisera digitala foton. Fotona organiseras i album som kan sorteras kronologiskt, enligt katalogutläggning eller enligt egna samlingar.

-

digiKam, resimlerinizi içe aktarma ve düzenlemeyi aniden yapmanızı sağlayan bir gelişmiş resim yönetme uygulamasıdır. Resimleriniz albümlerde düzenlenir ve bu albümler tarihe göre sıralanıp, dizin düzeninde ya da özelleşmiş koleksiyonlarda gösterilebilir.

-

digiKam — програма з розширеними можливостями щодо керування цифровими фотографіями, яка робить імпортування і впорядкування цифрових фотографій зовсім простою справою". Програма впорядковує фотографії за альбомами, які можна розташовувати у вікні програму у хронологічному порядку, за теками або за нетиповими збірками.

-

xxdigiKam is an advanced digital photo management application, which makes importing and organizing digital photos a "snap". The photos are organized in albums which can be sorted chronologically, by folder layout or by custom collections.xx

-

digiKam 是一种先进的数字照片管理应用程序,它使导入和组织数字照片变得轻而易举。照片按照时间顺序、文件夹布局或自定义集合来排列。

+

xxExtended functionality in digiKam is implemented via a set of tools based of plugins mechanism (named DPlugins for digiKam Plugins). Plugins can be written to import and export contents to remote web-services, add new features to edit image, and batch process photo.xx

Features:

الميزات:

Svojstva:

Característiques:

Característiques:

Vlastnosti:

Funktioner:

Funktionen:

Χαρακτηριστικά:

Features:

Funciones:

Omadused:

Ominaisuuksia:

Fonctionnalités :

Funcionalidades:

Fitur:

Caratteristiche:

기능:

Mogelijkheden:

Funksjonar:

ਫੀਚਰ:

Możliwości:

Funcionalidades:

Funcionalidades:

Возможности:

Funkcie:

Zmožnosti:

Funktioner:

Özellikler:

Можливості:

xxFeatures:xx

功能:

功能:

  • Organization of photos in albums and sub-albums (with tags and comments support)
  • تنظيم الصّور في ألبومات وألبومات فرعيّة (بدعم الوسوم والتّعليقات)
  • Organizacija fotografija u albume i podalbume (sa podrškom za označavanje i komentarisanje)
  • Organització de les fotografies en àlbums i subàlbums (amb implementació per a les etiquetes i comentaris)
  • Organització de les fotografies en àlbums i subàlbums (amb implementació per a les etiquetes i comentaris)
  • Organizace fotografií do alb a podřízených alb s podporou značek a komentářů
  • Organisation von Fotos in Alben und Unteralben mit Unterstützung für Stichwörter und Kommentare
  • Οργάνωση φωτογραφιών σε άλμπουμ και υπο-άλμπουμ (με υποστήριξη για ετικέτες και σχόλια)
  • Organisation of photos in albums and sub-albums (with tags and comments support)
  • Organización de fotos en álbumes y álbumes anidados (con uso de etiquetas y comentarios)
  • Fotode paigutamine albumitesse ja alamalbumitesse (toetatud on sildid ja kommentaarid)
  • Valokuvien järjestely albumeiksi ja alialbumeiksi (tunniste- ja kommenttituella)
  • Organisation de photos en albums et sous-albums (avec prise en charge des étiquettes et des commentaires)
  • Organización de fotos en álbums e subálbums (con funcionalidade de etiquetas e comentarios).
  • Organisasi foto dalam album dan sub-album (dengan dukungan tag dan komentar)
  • Organizzazione di foto in album e sotto-album (con supporto per le etichette e i commenti)
  • 앨범 및 하위 앨범으로 사진 정렬(태그와 주석 지원)
  • Organiseren van foto's in albums en sub-albums (met tags en ondersteuning van commentaar)
  • Organisering av bilete i album og underalbum (med støtte for merkelappar og kommentarar)
  • Grupowanie zdjęć w albumy i podalbumów (z obsługą znaczników i komentarzy)
  • Organização das fotografias por álbuns e sub-álbuns (com o suporte para marcas e comentários)
  • Organização de fotos em álbuns e subálbuns (com suporte a etiquetas e comentários)
  • Упорядочивание фотографий с использованием иерархических альбомов, меток и комментариев;
  • Organizácia fotiek v albumoch a podalbumoch (so značkami a komentármi)
  • Organizacija fotografij v albumih in pod-albumih (s podporo oznakam in opombam)
  • Organisation av foton i album och delalbum (med stöd för etiketter och kommentarer)
  • Resimler albüm ya da alt albümlerde düzenleme (etiket ve yorum desteğiyle)
  • упорядковування фотографій за альбомами і підальбомами з підтримкою міток та коментарів;
  • xxOrganization of photos in albums and sub-albums (with tags and comments support)xx
  • 在相册和子相册中组织照片 (支持标签和注释)
  • Support for Exif, Iptc, Xmp, Makernotes
  • Podrška za Exif, Iptc, Xmp, Makernotes
  • Implementació per a notes EXIF, IPTC, XMP i del fabricant
  • Implementació per a notes EXIF, IPTC, XMP i del fabricant
  • Podpora pro Exif, Iptc, Xmp, Makernotes
  • Unterstützung für Exif, Iptc, Xmp und Herstellerbemerkungen
  • Υποστήριξη για Exif, Iptc, Xmp, Makernotes
  • Support for Exif, Iptc, Xmp, Makernotes
  • Uso de Exif, Iptc, Xmp y notas del fabricante
  • Exif'i, Iptc, Xmp, Makernotes'i toetus
  • Exif-, IPTC-, XMP ja Makernotes-tuki
  • Prise en charge des données EXIF, IPTC, XMP, MakerNotes
  • Compatíbel con Exif, Iptc, Xmp e Makernotes.
  • Mendukung Exif. Iptc, Xmp, Makernotes
  • Supporto per Exif, Iptc, Xmp, Makernotes
  • EXIF, IPTC, XMP, 메이커 노트 지원
  • Ondersteuning voor Exif, Iptc, Xmp, Makernotes
  • Støtte for Exif, Iptc, Xmp og Makernotes
  • Obsługa Exif, Iptc, Xmp, notatek twórcy
  • Suporte para o EXIF, IPTC, XMP e notas do fabricante
  • Suporte a EXIF, IPTC, XMP e Notas do Fabricante
  • Поддержка метаданных EXIF, IPTC, XMP и MakerNote;
  • Podpora pre Exif, Iptc, Xmp, Makernotes
  • Podpora Exif, Iptc, Xmp, Makernotes
  • Stöd för EXIF, IPTC, XMP, tillverkaranteckningar
  • Exif, Iptc, Xmp, Makernotes desteği
  • підтримка Exif, Iptc, Xmp, Makernotes;
  • xxSupport for Exif, Iptc, Xmp, Makernotesxx
  • 支持 Exif、Iptc、Xmp、Makernotes
  • 支援 EXIF、IPTC、XMP 和 Makernotes
  • SQLite or Mysql powered storage for the album contents and its metadata with advanced search tools
  • Emmagatzematge basat en SQLite o Mysql per als continguts dels àlbums i les seves metadades amb eines de cerca avançades
  • Emmagatzematge basat en SQLite o Mysql per als continguts dels àlbums i les seues metadades amb eines de cerca avançades
  • Ukládání informace o albech do databáze založené na SQLite nebo MySQL s pokročilými vyhledávacími nástroji
  • Αποθηκευτικός χώρος με SQLite ή Mysql για τα άλμπουμ και τα μεταδεδομένα τους με προηγμένα εργαλεία αναζήτησης
  • SQLite or Mysql powered storage for the album contents and its metadata with advanced search tools
  • Almacenamiento basado en SQLite o Mysql para los álbumes y sus metadatos con herramientas de búsqueda avanzadas
  • SQLite'i või Mysql'i baasil salvesti albumite sisule ja metaandmetele ühes võimsa otsingutööriistaga
  • SQLite- tai MySQL-tallennustila albumien sisällölle ja matatiedoille sekä edistyneet hakutyökalut
  • Stockage du contenu des albums et des métadonnées fonctionnant avec SQLite ou MySQL et des outils de recherche avancée
  • Almacenamento mediante SQLite ou MySQL para o contido dos álbums e os seus metadatos con ferramentas de busca avanzadas.
  • Penyimpanan yang diberdayakan SQLite atau Mysql untuk konten album dan metadatanya dengan alat pencarian tingkat-lanjut
  • Memorizzazione del contenuto degli album e dei suoi metadati fornita da SQLite o Mysql, con strumenti di ricerca avanzati
  • SQLite 및 MySQL 기반 앨범과 메타데이터 저장소 및 고급 검색 도구
  • Op SQLite of Mysql gebaseerde opslag voor de inhoud van albums en zijn metagegevens met geavanceerde zoekhulpmiddellen
  • SQLite- eller MySQL-basert lagring for album og metadata, med avanserte søkjeverktøy
  • Przechowalnia dla albumów i ich metadanych z zaawansowanymi narzędziami wyszukiwania jest napędzana SQLite lub Mysql
  • Armazenamento baseado em SQLite ou MySQL para o conteúdo dos álbuns e os seus meta-dados com ferramentas de pesquisa avançadas
  • Armazenamento baseado em SQLite ou MySQL para o conteúdo dos álbuns e seus metadados com ferramentas de pesquisa avançadas
  • Использование баз данных SQLite и MySQL для хранения метаданных и информации о содержимом альбомов позволяет выполнять расширенный поиск и отбор требуемых изображений;
  • Shramba SQLite ali Mysql za albume in metapodatke ter napredna iskalna orodja
  • Lagring driven av SQLite eller MySQL för album och deras metadata med avancerade sökverktyg
  • Albüm içeriği ve meta verileri için gelişmiş arama araçlarıyla, SQLite veya Mysql destekli depolama
  • сховище даних на основі SQLite або MySQL для вмісту альбомів та метаданих із розширеними можливостями пошуку;
  • xxSQLite or Mysql powered storage for the album contents and its metadata with advanced search toolsxx
  • SQLite 或 Mysql 为相册内容及其元数据提供高级搜索工具的存储
  • Support for filtering and sorting albums
  • دعم ترشيح الألبومات وفرزها
  • Podrška za filtriranje i sortiranje albuma
  • Implementació pel filtratge i classificació dels àlbums
  • Implementació pel filtratge i classificació dels àlbums
  • Podpora řazení a filtrování alb
  • Unterstützung für das Filtern und Sortieren von Alben
  • Υποστήριξη για φιλτράρισμα και ταξινόμηση των άλμπουμ
  • Support for filtering and sorting albums
  • Implementación de filtrado y ordenación de álbumes
  • Albumite filtreerimise ja sortimise toetus
  • Albumien suodatuksen ja lajittelun tuki
  • Prise en charge du filtrage et du tri des albums
  • Permite filtrar e ordenar os álbums.
  • Mendukung pemfilteran dan pengurutan album
  • Supporto per il filtraggio e l'ordinamento degli album
  • 앨범 필터링 및 정렬
  • Ondersteuning voor filtering en sorteren van albums
  • Støtte for filtrering og sortering av album
  • Obsługa filtrowania i sortowania albumów
  • Suporte para filtrar e ordenar os álbuns
  • Suporte a filtragem e ordenação de álbuns
  • Отбор и сортировка альбомов;
  • Podpora pre filtrovanie a triedenie albumov
  • Podpora filtriranju in razvrščanju albumov
  • Stöd för filtrering och sortering av album
  • Albümleri filtreleme ve sıralama desteği
  • підтримка фільтрування та упорядковування альбомів;
  • xxSupport for filtering and sorting albumsxx
  • 支持筛选和排序相册
  • 支援篩選和排序相簿
  • Import from more than 1100 digital camera devices
  • الاستيراد من أكثر من 1100 كمرة رقميّة
  • Uvoz iz više od 1100 digitalnih kamera uređaja
  • Importa des de més de 1.100 dispositius de càmeres digitals
  • Importa des de més de 1.100 dispositius de càmeres digitals
  • Import z více než 1100 typů digitálních fotoaparátů
  • Import von mehr als 1100 digitalen Kameras
  • Εισαγωγή από περισσότερες από 1100 ψηφιακές κάμερες
  • Import from more than 1100 digital camera devices
  • Permite importar de más de 1100 cámaras digitales
  • Import enam kui 1100 digitaalkaamerast
  • Tuontituki yli 1100 eri digikameralle
  • Importe à partir de plus de 1100 appareils photos numériques
  • Permite importar desde máis de 1100 dispositivos de cámara dixital.
  • Impor dari lebih dari 110 perangkat kamera digital
  • Importazione da più di 1.100 dispositivi fotografici digitali
  • 1100종 이상의 디지털 카메라에서 사진 가져오기
  • Importeren uit meer dan 1100 digitale camera's
  • Støtte for importering av bilete frå meir enn 1 100 ulike kameramodellar
  • Importowanie z więcej niż 1100 aparatów cyfrowych
  • Importar a partir de mais de 1100 máquinas digitais
  • Importação de mais de 1.100 câmeras digitais
  • Поддержка импорта более чем с 1100 моделей цифровых камер;
  • Import z viac ako 1100 digitálnych fotoaparátov
  • Uvažanje iz več kot 1100 digitalnih fotoaparatov
  • Import från mer än 1100 digitalkameror
  • 1100den fazla kamera cihazından içe aktarma
  • можливість імпортування даних з понад 1100 цифрових фотоапаратів;
  • xxImport from more than 1100 digital camera devicesxx
  • 从1100多种数码相机设备中导入
  • 能從超過 1100 個數位相機裝置匯入
  • Support for more than 900 RAW format pictures
  • Admet més de 900 formats d'imatge RAW
  • Admet més de 900 formats d'imatge RAW
  • Podpora více než 900 obrázků ve formátu RAW
  • Unterstützung für mehr als 900 Bild-Rohformate
  • Υποστήριξη για περισσότερους από 900 τύπους RAW εικόνων
  • Support for more than 900 RAW format pictures
  • Permite imágenes en más de 900 formatos RAW
  • Enam kui 900 toorpildivormingu toetus
  • Yli 900 RAW-kuvamuodon tuki
  • Prise en charge de plus de 900 formats d'image RAW
  • Compatíbel con imaxes en máis de 900 formatos en cru (RAW).
  • Mendukung lebih dari 900 gambar format RAW
  • Supporto per più di 900 immagini in formato RAW
  • 900종 이상의 RAW 사진 형식 지원
  • Ondersteuning voor 900 RAW-format afbeeldingen
  • Støtte for meir enn 900 ulike råbiletformat
  • Obsługa dla więcej niż 900 postaci zdjęć RAW
  • Suporte para mais de 900 formatos de imagens RAW
  • Suporte a mais de 900 formatos de imagem RAW
  • Поддержка более 900 форматов цифровых негативов (RAW);
  • Podpora več kot 900 vrstam surovih slik (RAW)
  • Stöd för 900 obehandlade bildformat
  • 900'den fazla RAW biçimli resim desteği
  • підтримка понад 900 форматів цифрових негативів (RAW);
  • xxSupport for more than 900 RAW format picturesxx
  • 支持超过900种 RAW 格式的图片
  • 支援超過 900 個 RAW 格式圖片
  • Light Table to compare photo side by side
  • Taula de llum per a comparar les fotografies costat a costat
  • Taula de llum per a comparar les fotografies costat a costat
  • Prohlížecí stolek pro porovnání fotografií vedle sebe
  • Leuchttisch zum Vergleichen von Fotos nebeneinander
  • Ελαφρύς πίνακας για σύγκριση φωτογραφιών κατ' αντιπαράθεση
  • Light Table to compare photo side by side
  • Mesa de luz para comparar fotos una junto a otra
  • Valguslaud fotode vahetuks võrdlemiseks
  • Valotaulu kuvien vertailemiseksi rinnakkain
  • Table lumineuse pour comparer des photos côte-à-côte
  • Táboa de luz para comparar fotos unha ao lado da outra.
  • Table Terang untuk membandingkan foto sisi demi sisi
  • Tavolo luminoso per confrontare le foto fianco a fianco
  • 사진을 양쪽으로 비교하는 조명 테이블
  • Lichttafel om foto's naast elkaar te vergelijken
  • Lysbord for å samanlikna bilete ved sida av kvarandre
  • Podświetlarka do porównywania zdjęć jedno obok drugiego
  • Mesa de Luz para comparar as fotografias lado-a-lado
  • Mesa de Luz para comparação de fotos lado a lado
  • Световой стол для сравнения изображений;
  • Osvetlitvena miza za primerjavo fotografij
  • Ljusbord för att jämföra foton sida vid sida
  • Fotoğrafı yan yana karşılaştırmak için Tabloyu Işıklandır
  • стіл із підсвічуванням для порівняння фотографій;
  • xxLight Table to compare photo side by sidexx
  • 用于并排比较照片的灯桌
  • Extended features using extra tools
  • Característiques ampliades usant eines addicionals
  • Característiques ampliades usant eines addicionals
  • Rozšířené vlastnosti pomocí extra nástrojů
  • Επεκτάσεις χαρακτηριστικών με χρήση επιπλέον εργαλείων
  • Extended features using extra tools
  • Funciones extendidas mediante herramientas adicionales
  • Avarad võimalused lisatööriistade abil
  • Lisää ominaisuuksia lisätyökaluilla
  • Fonctionnalités étendues par des outils externes
  • Funcionalidades estendidas usando ferramentas adicionais.
  • Fitur yang diperluas menggunakan peralatan extra
  • Funzionalità estese tramite strumenti aggiuntivi
  • 추가 도구로 기능 확장
  • Uitgebreide mogelijkheden met extra hulpmiddelen
  • Programtillegg som gjev ekstrafunksjonar
  • Rozszerzone funkcje używające dodatkowych narzędzi
  • Funcionalidades estendidas com ferramentas extra
  • Funcionalidades estendidas com ferramentas extras
  • Расширенные возможности дополнительных инструментов;
  • Razširjene zmožnosti preko dodatnih orodij
  • Utökade funktioner med användning av extra verktyg
  • Ekstra araçlar kullanan genişletilmiş özellikler
  • розширення можливостей за допомогою додаткових інструментів;
  • xxExtended features using extra toolsxx
  • 使用额外工具的扩展功能
  • 使用延伸工具得到延伸功能
  • Share your photos using HTML or publishing them to remote web services
  • Compartiu les vostres fotografies usant HTML o publicant-les a serveis web remots
  • Compartiu les vostres fotografies usant HTML o publicant-les a serveis web remots
  • Sdílení fotografií vytvořením HTML galerie nebo přímým publikováním na vzdálených webových službách
  • Teilen Sie Ihre Fotos über HTML oder veröffentlichen Sie sie auf entfernten Webdiensten
  • Μοιραστείτε τις φωτογραφίες σας με HTML ή με δημοσίευση σε απομακρυσμένες υπηρεσίες ιστού
  • Share your photos using HTML or publishing them to remote web services
  • Compartir fotos usando HTML o publicándolas en servicios web remotos
  • Fotode jagamine HTML-i vahendusel või nende avaldamine veebiteenustes
  • Valokuvien jako HTML:ksi tai julkaisu etäverkkopalvelimiin
  • Permet de partager vos photos avec des pages HTML ou de les publier sur des services web distants
  • Permite compartir imaxes usando HTML ou publicándoas en servizos web remotos.
  • Bagikan fotomu menggunakan HTML atau mempublikasikannya ke layanan web jarak jauh
  • Condivisione delle foto tramite HTML o loro pubblicazione nei servizi web remoti
  • HTML로 사진 공유 및 웹 서비스에 올리기
  • Uw foto's delen met HTML of ze publiceren op websites op afstand
  • Kan dela bileta med HTML eller publisera dei på ulike nett-tenester
  • Udostępnianie zdjęć przy użyciu HTML lub publikowanie ich poprzez usługi sieciowe
  • Partilhar as suas fotos por HTML ou publicá-las em serviços Web remotos
  • Compartilhamento de fotos usando HTML ou publicação em serviços remotos
  • Публикация фотографий созданием галерей в формате HTML а также на различных интернет-ресурсах;
  • Delite vaše fotografije s pomočjo HTML-ja ali pa jih objavite na oddaljenih storitvah
  • Dela foton genom att använda HTML eller publicera dem på webbtjänster
  • Resimlerinizi HTML kullanarak paylaşın veya onları uzak web servislerinde yayımlayın
  • можливість оприлюднення фотографій на віддалених службах у інтернеті;
  • xxShare your photos using HTML or publishing them to remote web servicesxx
  • 使用 HTML 共享您的照片或将其发布到远程 WEB 服务
  • 使用 HTML 分享圖片或將圖片發布到遠端網路服務
  • Image editor included with advanced tools to fix photo quickly
  • Inclou un editor d'imatges amb eines avançades per esmenar ràpidament les fotografies
  • Inclou un editor d'imatges amb eines avançades per esmenar ràpidament les fotografies
  • Editor obrázků zahrnutý v pokročilých nástrojích pro rychlé opravy fotek
  • Περιλαμβάνεται επεξεργαστής εικόνων με προηγμένα εργαλεία για γρήγορη διόρθωση φωτογραφιών
  • Image editor included with advanced tools to fix photo quickly
  • Editor de imágenes incluido con herramientas avanzadas para corregir las fotos de forma rápida
  • Pildiredaktor võimsate tööriistadega fotode kiireks parandamiseks
  • Lisätyökaluissa kuvamuokkain kuvien pikakorjauksiin
  • Éditeur d'images inclus, avec des fonctionnalités pour réparer rapidement une photo
  • Inclúe un editor de imaxes con ferramentas avanzadas para arranxar fotos rapidamente.
  • Tersemat editor citra dengan alat tingkat-lanjut untuk memperbaiki foto secara cepat
  • Editor delle immagini con strumenti avanzati inclusi per la correzione rapida delle foto
  • 사진을 빠르게 편집할 수 있는 고급 기능이 있는 그림 편집기
  • Bewerker van afbeeldingen met geavanceerde hulpmiddelen om foto's snel te repareren
  • Innebygd biletredigering med avanserte verktøy for å forbetra bileta
  • Dołączony edytor obrazów o zaawansowanych narzedziach do szybkiego poprawiania zdjęć
  • Editor de imagens que inclui ferramentas avançadas para corrigir rapidamente as fotografias
  • Editor de imagens com funcionalidades avançadas para correção de fotos
  • Редактор изображений с расширенными инструментами для быстрого исправления фотографий.
  • Vgrajen urejevalnik slik z naprednimi orodji za hitre popravke fotografij
  • Bildeditor med avancerade funktioner ingår för att snabbt förbättra foton
  • Fotoğrafı hızla düzeltmek için gelişmiş araçlar içeren resim editörü
  • редактор зображень із додатковими можливостями для швидкого виправлення фотографій;
  • xxImage editor included with advanced tools to fix photo quicklyxx
  • 图像编辑器包含高级工具以快速修复照片
  • 包含進階工具的圖片編輯器以快速修復圖片
https://www.digikam.org/ https://bugs.kde.org/enter_bug.cgi?format=guided&product=digikam https://docs.kde.org/?application=digikam https://www.digikam.org/donation digiKam Batch Queue Manager xxdigiKam Batch Queue Managerxx https://www.digikam.org/img/content/about/overview_bqm.png digiKam Image Editor xxdigiKam Image Editorxx https://www.digikam.org/img/content/about/overview_imageeditor.png digiKam Import From Camera xxdigiKam Import From Cameraxx https://www.digikam.org/img/content/about/overview_import.png digiKam Light Table xxdigiKam Light Tablexx https://www.digikam.org/img/content/about/overview_lighttable.png digiKam Tags View - Gestió de les fotografies al digiKam - Gestió de les fotografies al digiKam - Správa fotografií v digiKamu - Fotoverwaltung in digiKam - Photo management in digiKam - Gestión de fotos en digiKam - Fotohaldus digiKamis - Valokuvien hallinta digiKamissä - Gestion de photos dans digiKam - Xestión de fotos en digiKam - Pengelolaan foto dalam digiKam - Gestione delle foto in digiKam - digiKam으로 사진 관리하기 - Fotobeheer in digiKam - Fotohandsaming i digiKam - ਡਿਜਿਕੈਮ ਵਿੱਚ ਫੋਟੋ ਇੰਤਜ਼ਾਮ - Zarządzanie zdjęciami w digiKam - Gestão de Fotografias no digiKam - Gerenciamento de fotos no digiKam - Управление фотографиями в digiKam - Program za upravljanje zbirk fotografij - Fotohantering i Digikam - Керування фотографіями у digiKam xxdigiKam Tags Viewxx - digiKam 的照片管理 - digiKam 中的圖片管理 https://www.digikam.org/img/content/about/overview_tagsview.png + + + + + + + + + + + digiKam Team - مشروع «دِجي‌كام» - digiKam Projekt - Projecte digiKam - Projecte digiKam - Projekt digiKam - digiKam-Projekt - digiKam Project - digiKam Project - Proyecto digiKam - digiKami projekt - digiKam-projekti - Projet digiKam - Proxecto digiKam - digiKam Project - Progetto digiKam - digiKam 프로젝트 - digiKam-project - digiKam-prosjektet - ਡਿਜਿਕੈਮ ਪਰੋਜੈੱਕਟ - Projekt digiKam - Projecto digiKam - Projeto digiKam - Проект digiKam - Projekt digiKamu - Projekt digiKam - Digikam-projektet - digiKam Projesi - Проєкт digiKam xxdigiKam Teamxx - digiKam 项目 - digiKam 專案 KDE digikam
diff --git a/core/cmake/templates/DigikamPluginConfig.cmake.in b/core/cmake/templates/DigikamPluginConfig.cmake.in new file mode 100644 index 0000000000..b0b0f76659 --- /dev/null +++ b/core/cmake/templates/DigikamPluginConfig.cmake.in @@ -0,0 +1,16 @@ +# +# Copyright (c) 2010-2020 by Gilles Caulier, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +SET(DPluginGenericInstallPath "${PLUGIN_INSTALL_DIR}/digikam/generic") + +SET(DPluginEditorInstallPath "${PLUGIN_INSTALL_DIR}/digikam/editor") + +SET(DPluginBqmInstallPath "${PLUGIN_INSTALL_DIR}/digikam/bqm") + +SET(DPluginRawImportInstallPath "${PLUGIN_INSTALL_DIR}/digikam/rawimport") + +SET(DPluginDimgInstallPath "${PLUGIN_INSTALL_DIR}/digikam/dimg") diff --git a/core/dplugins/dimg/jpeg/dimgjpegloader_load.cpp b/core/dplugins/dimg/jpeg/dimgjpegloader_load.cpp index 27863fd998..638b3052b2 100644 --- a/core/dplugins/dimg/jpeg/dimgjpegloader_load.cpp +++ b/core/dplugins/dimg/jpeg/dimgjpegloader_load.cpp @@ -1,630 +1,631 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-06-14 * Description : A JPEG IO file for DImg framework - load operations * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2005-2020 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. * * ============================================================ */ #define XMD_H #include "dimgjpegloader.h" // C ANSI includes extern "C" { #include "iccjpeg.h" } // Qt includes #include #include // Local includes #include "digikam_debug.h" #include "digikam_config.h" #include "dimgloaderobserver.h" #ifdef Q_OS_WIN # include "jpegwin.h" #endif namespace DigikamJPEGDImgPlugin { bool DImgJPEGLoader::load(const QString& filePath, DImgLoaderObserver* const observer) { readMetadata(filePath); #ifdef Q_OS_WIN FILE* const file = _wfopen((const wchar_t*)filePath.utf16(), L"rb"); #else FILE* const file = fopen(filePath.toUtf8().constData(), "rb"); #endif if (!file) { loadingFailed(); return false; } unsigned char header[2]; if (fread(&header, 2, 1, file) != 1) { fclose(file); loadingFailed(); return false; } unsigned char jpegID[] = { 0xFF, 0xD8 }; if (memcmp(header, jpegID, 2) != 0) { // not a jpeg file fclose(file); loadingFailed(); return false; } rewind(file); struct jpeg_decompress_struct cinfo; struct dimg_jpeg_error_mgr jerr; // ------------------------------------------------------------------- // JPEG error handling. cinfo.err = jpeg_std_error(&jerr); cinfo.err->error_exit = dimg_jpeg_error_exit; cinfo.err->emit_message = dimg_jpeg_emit_message; cinfo.err->output_message = dimg_jpeg_output_message; // setjmp-save cleanup class Q_DECL_HIDDEN CleanupData { public: CleanupData() : data(nullptr), dest(nullptr), file(nullptr), cmod(0) { } ~CleanupData() { delete [] data; delete [] dest; if (file) { fclose(file); } } void setData(uchar* const d) { data = d; } void setDest(uchar* const d) { dest = d; } void setFile(FILE* const f) { file = f; } void setSize(const QSize& s) { size = s; } void setColorModel(int c) { cmod = c; } void deleteData() { delete [] data; data = nullptr; } void takeDest() { dest = nullptr; } public: uchar* data; uchar* dest; FILE* file; QSize size; int cmod; }; CleanupData* const cleanupData = new CleanupData; cleanupData->setFile(file); // If an error occurs during reading, libjpeg will jump here if (setjmp(jerr.setjmp_buffer)) { jpeg_destroy_decompress(&cinfo); if (!cleanupData->dest || !cleanupData->size.isValid()) { delete cleanupData; loadingFailed(); return false; } // We check only Exif metadata for ICC profile to prevent endless loop if (m_loadFlags & LoadICCData) { checkExifWorkingColorSpace(); } if (observer) { observer->progressInfo(1.0); } imageWidth() = cleanupData->size.width(); imageHeight() = cleanupData->size.height(); imageData() = cleanupData->dest; imageSetAttribute(QLatin1String("format"), QLatin1String("JPG")); imageSetAttribute(QLatin1String("originalColorModel"), cleanupData->cmod); imageSetAttribute(QLatin1String("originalBitDepth"), 8); imageSetAttribute(QLatin1String("originalSize"), cleanupData->size); cleanupData->takeDest(); delete cleanupData; return true; } // ------------------------------------------------------------------- // Find out if we do the fast-track loading with reduced size. Jpeg specific. int scaledLoadingSize = 0; QVariant attribute = imageGetAttribute(QLatin1String("scaledLoadingSize")); if (attribute.isValid()) { scaledLoadingSize = attribute.toInt(); } // ------------------------------------------------------------------- // Set JPEG decompressor instance jpeg_create_decompress(&cinfo); bool startedDecompress = false; jpeg_stdio_src(&cinfo, file); // Recording ICC profile marker (from iccjpeg.c) if (m_loadFlags & LoadICCData) { setup_read_icc_profile(&cinfo); } // read image information jpeg_read_header(&cinfo, boolean(true)); // read dimension (nominal values from header) int w = cinfo.image_width; int h = cinfo.image_height; QSize originalSize(w, h); // Libjpeg handles the following conversions: // YCbCr => GRAYSCALE, YCbCr => RGB, GRAYSCALE => RGB, YCCK => CMYK // So we cannot get RGB from CMYK or YCCK, CMYK conversion is handled below int colorModel = DImg::COLORMODELUNKNOWN; switch (cinfo.jpeg_color_space) { case JCS_UNKNOWN: // perhaps jpeg_read_header did some guessing, leave value unchanged colorModel = DImg::COLORMODELUNKNOWN; break; case JCS_GRAYSCALE: cinfo.out_color_space = JCS_RGB; colorModel = DImg::GRAYSCALE; break; case JCS_RGB: cinfo.out_color_space = JCS_RGB; colorModel = DImg::RGB; break; case JCS_YCbCr: cinfo.out_color_space = JCS_RGB; colorModel = DImg::YCBCR; break; case JCS_CMYK: case JCS_YCCK: cinfo.out_color_space = JCS_CMYK; colorModel = DImg::CMYK; break; default: break; } cleanupData->setColorModel(colorModel); // ------------------------------------------------------------------- // Load image data. uchar* dest = nullptr; if (m_loadFlags & LoadImageData) { // set decompression parameters cinfo.do_fancy_upsampling = boolean(true); cinfo.do_block_smoothing = boolean(false); // handle scaled loading if (scaledLoadingSize) { int imgSize = qMax(cinfo.image_width, cinfo.image_height); + int scale = 1; // libjpeg supports 1/1, 1/2, 1/4, 1/8 - int scale = 1; - while (scaledLoadingSize* scale * 2 <= imgSize) + while (scaledLoadingSize * scale * 2 <= imgSize) { scale *= 2; } if (scale > 8) { scale = 8; } - - //cinfo.scale_num = 1; - //cinfo.scale_denom = scale; +/* + cinfo.scale_num = 1; + cinfo.scale_denom = scale; +*/ cinfo.scale_denom *= scale; } // initialize decompression if (!startedDecompress) { jpeg_start_decompress(&cinfo); startedDecompress = true; } // some pseudo-progress if (observer) { observer->progressInfo(0.1F); } // re-read dimension (scaling included) w = cinfo.output_width; h = cinfo.output_height; // ------------------------------------------------------------------- // Get scanlines uchar* ptr = nullptr, *data = nullptr, *line[16]; uchar* ptr2 = nullptr; int x, y, l, i, scans; // int count; // int prevy; if (cinfo.rec_outbuf_height > 16) { jpeg_destroy_decompress(&cinfo); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "Height of JPEG scanline buffer out of range!"; delete cleanupData; loadingFailed(); return false; } // We only take RGB with 1 or 3 components, or CMYK with 4 components if (!( (cinfo.out_color_space == JCS_RGB && (cinfo.output_components == 3 || cinfo.output_components == 1)) || (cinfo.out_color_space == JCS_CMYK && cinfo.output_components == 4) )) { jpeg_destroy_decompress(&cinfo); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "JPEG colorspace (" << cinfo.out_color_space << ") or Number of JPEG color components (" << cinfo.output_components << ") unsupported!"; delete cleanupData; loadingFailed(); return false; } data = new_failureTolerant(w * 16 * cinfo.output_components); cleanupData->setData(data); if (!data) { jpeg_destroy_decompress(&cinfo); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "Cannot allocate memory!"; delete cleanupData; loadingFailed(); return false; } dest = new_failureTolerant(w, h, 4); cleanupData->setSize(QSize(w, h)); cleanupData->setDest(dest); if (!dest) { jpeg_destroy_decompress(&cinfo); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "Cannot allocate memory!"; delete cleanupData; loadingFailed(); return false; } ptr2 = dest; // count = 0; // prevy = 0; if (cinfo.output_components == 3) { for (i = 0; i < cinfo.rec_outbuf_height; ++i) { line[i] = data + (i * w * 3); } int checkPoint = 0; for (l = 0; l < h; l += cinfo.rec_outbuf_height) { // use 0-10% and 90-100% for pseudo-progress if (observer && l >= checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery()) { jpeg_destroy_decompress(&cinfo); delete cleanupData; loadingFailed(); return false; } observer->progressInfo(0.1 + (0.8 * (((float)l) / ((float)h)))); } jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height); scans = cinfo.rec_outbuf_height; if ((h - l) < scans) { scans = h - l; } ptr = data; for (y = 0; y < scans; ++y) { for (x = 0; x < w; ++x) { ptr2[3] = 0xFF; ptr2[2] = ptr[0]; ptr2[1] = ptr[1]; ptr2[0] = ptr[2]; ptr += 3; ptr2 += 4; } } } } else if (cinfo.output_components == 1) { for (i = 0; i < cinfo.rec_outbuf_height; ++i) { line[i] = data + (i * w); } int checkPoint = 0; for (l = 0; l < h; l += cinfo.rec_outbuf_height) { if (observer && l >= checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery()) { jpeg_destroy_decompress(&cinfo); delete cleanupData; loadingFailed(); return false; } observer->progressInfo(0.1 + (0.8 * (((float)l) / ((float)h)))); } jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height); scans = cinfo.rec_outbuf_height; if ((h - l) < scans) { scans = h - l; } ptr = data; for (y = 0; y < scans; ++y) { for (x = 0; x < w; ++x) { ptr2[3] = 0xFF; ptr2[2] = ptr[0]; ptr2[1] = ptr[0]; ptr2[0] = ptr[0]; ptr ++; ptr2 += 4; } } } } else // CMYK { for (i = 0; i < cinfo.rec_outbuf_height; ++i) { line[i] = data + (i * w * 4); } int checkPoint = 0; for (l = 0; l < h; l += cinfo.rec_outbuf_height) { // use 0-10% and 90-100% for pseudo-progress if (observer && l >= checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery()) { jpeg_destroy_decompress(&cinfo); delete cleanupData; loadingFailed(); return false; } observer->progressInfo(0.1 + (0.8 * (((float)l) / ((float)h)))); } jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height); scans = cinfo.rec_outbuf_height; if ((h - l) < scans) { scans = h - l; } ptr = data; for (y = 0; y < scans; ++y) { for (x = 0; x < w; ++x) { // Inspired by Qt's JPEG loader int k = ptr[3]; ptr2[3] = 0xFF; ptr2[2] = k * ptr[0] / 255; ptr2[1] = k * ptr[1] / 255; ptr2[0] = k * ptr[2] / 255; ptr += 4; ptr2 += 4; } } } } // clean up cleanupData->deleteData(); } // ------------------------------------------------------------------- // Read image ICC profile if (m_loadFlags & LoadICCData) { if (!startedDecompress) { jpeg_start_decompress(&cinfo); startedDecompress = true; } JOCTET* profile_data = nullptr; uint profile_size = 0; read_icc_profile(&cinfo, &profile_data, &profile_size); if (profile_data != nullptr) { QByteArray profile_rawdata; profile_rawdata.resize(profile_size); memcpy(profile_rawdata.data(), profile_data, profile_size); imageSetIccProfile(IccProfile(profile_rawdata)); free(profile_data); } else { // If ICC profile is null, check Exif metadata. checkExifWorkingColorSpace(); } } // ------------------------------------------------------------------- if (startedDecompress) { jpeg_finish_decompress(&cinfo); } jpeg_destroy_decompress(&cinfo); // ------------------------------------------------------------------- cleanupData->takeDest(); delete cleanupData; if (observer) { observer->progressInfo(1.0); } imageWidth() = w; imageHeight() = h; imageData() = dest; imageSetAttribute(QLatin1String("format"), QLatin1String("JPG")); imageSetAttribute(QLatin1String("originalColorModel"), colorModel); imageSetAttribute(QLatin1String("originalBitDepth"), 8); imageSetAttribute(QLatin1String("originalSize"), originalSize); return true; } } // namespace DigikamJPEGDImgPlugin diff --git a/core/dplugins/generic/view/slideshow/common/slideshowloader.cpp b/core/dplugins/generic/view/slideshow/common/slideshowloader.cpp index 72b38442bb..8b97d99a8a 100644 --- a/core/dplugins/generic/view/slideshow/common/slideshowloader.cpp +++ b/core/dplugins/generic/view/slideshow/common/slideshowloader.cpp @@ -1,840 +1,842 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-04-21 * Description : slide show tool using preview of pictures. * * Copyright (C) 2005-2020 by Gilles Caulier * Copyright (C) 2004 by Enrico Ros * Copyright (C) 2019-2020 by Minh Nghia Duong * * 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 "slideshowloader.h" #include "digikam_config.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_DBUS # include # include # include #endif // KDE includes #include // Local includes #include "digikam_debug.h" #include "slidetoolbar.h" #include "slideimage.h" #include "slideerror.h" #include "slideosd.h" #include "slideend.h" #ifdef HAVE_MEDIAPLAYER # include "slidevideo.h" #endif //HAVE_MEDIAPLAYER using namespace Digikam; namespace DigikamGenericSlideShowPlugin { class Q_DECL_HIDDEN SlideShowLoader::Private { public: explicit Private() : fileIndex(-1), screenSaverCookie(-1), mouseMoveTimer(nullptr), imageView(nullptr), #ifdef HAVE_MEDIAPLAYER videoView(nullptr), #endif errorView(nullptr), endView(nullptr), osd(nullptr), settings(nullptr) { } int fileIndex; int screenSaverCookie; QTimer* mouseMoveTimer; ///< To hide cursor when not moved. SlideImage* imageView; #ifdef HAVE_MEDIAPLAYER SlideVideo* videoView; #endif SlideError* errorView; SlideEnd* endView; SlideOSD* osd; SlideShowSettings* settings; QMap shortcutPrefixes; }; SlideShowLoader::SlideShowLoader(SlideShowSettings* const settings) : QStackedWidget(nullptr), d(new Private) { d->settings = settings; setAttribute(Qt::WA_DeleteOnClose); setWindowFlags(Qt::FramelessWindowHint); setContextMenuPolicy(Qt::PreventContextMenu); setWindowState(windowState() | Qt::WindowFullScreen); setWindowTitle(i18n("Slideshow")); setObjectName(QLatin1String("Slideshow")); setMouseTracking(true); // --------------------------------------------------------------- d->errorView = new SlideError(this); d->errorView->installEventFilter(this); insertWidget(ErrorView, d->errorView); // --------------------------------------------------------------- d->imageView = new SlideImage(this); d->imageView->setPreviewSettings(d->settings->previewSettings); d->imageView->installEventFilter(this); connect(d->imageView, SIGNAL(signalImageLoaded(bool)), this, SLOT(slotImageLoaded(bool))); insertWidget(ImageView, d->imageView); // --------------------------------------------------------------- #ifdef HAVE_MEDIAPLAYER d->videoView = new SlideVideo(this); d->videoView->setInfoInterface(d->settings->iface); d->videoView->installEventFilter(this); connect(d->videoView, SIGNAL(signalVideoLoaded(bool)), this, SLOT(slotVideoLoaded(bool))); connect(d->videoView, SIGNAL(signalVideoFinished()), this, SLOT(slotVideoFinished())); insertWidget(VideoView, d->videoView); #endif // --------------------------------------------------------------- d->endView = new SlideEnd(this); d->endView->installEventFilter(this); insertWidget(EndView, d->endView); // --------------------------------------------------------------- d->osd = new SlideOSD(d->settings, this); d->osd->installEventFilter(this); // --------------------------------------------------------------- d->mouseMoveTimer = new QTimer(this); d->mouseMoveTimer->setSingleShot(true); d->mouseMoveTimer->setInterval(1000); connect(d->mouseMoveTimer, SIGNAL(timeout()), this, SLOT(slotMouseMoveTimeOut())); // --------------------------------------------------------------- QScreen* screen = qApp->primaryScreen(); if (QWidget* const widget = qApp->activeWindow()) { if (QWindow* const window = widget->windowHandle()) { screen = window->screen(); } } const int activeScreenIndex = qMax(qApp->screens().indexOf(screen), 0); const int preferenceScreen = d->settings->slideScreen; int screenIndex = 0; if (preferenceScreen == -2) { screenIndex = activeScreenIndex; } else if (preferenceScreen == -1) { QScreen* const primaryScreen = qApp->primaryScreen(); screenIndex = qApp->screens().indexOf(primaryScreen); } else if ((preferenceScreen >= 0) && (preferenceScreen < qApp->screens().count())) { screenIndex = preferenceScreen; } else { screenIndex = activeScreenIndex; d->settings->slideScreen = -2; d->settings->writeToConfig(); } slotScreenSelected(screenIndex); // --------------------------------------------------------------- inhibitScreenSaver(); slotMouseMoveTimeOut(); setCurrentIndex(ImageView); } SlideShowLoader::~SlideShowLoader() { emit signalLastItemUrl(currentItem()); d->mouseMoveTimer->stop(); allowScreenSaver(); delete d->settings; delete d; } void SlideShowLoader::setCurrentView(SlideShowViewMode view) { switch (view) { case ErrorView: { d->osd->video(false); d->errorView->setCurrentUrl(currentItem()); setCurrentIndex(view); d->osd->setCurrentUrl(currentItem()); break; } case ImageView: { #ifdef HAVE_MEDIAPLAYER d->videoView->stop(); d->osd->video(false); #endif setCurrentIndex(view); d->osd->setCurrentUrl(currentItem()); break; } case VideoView: { #ifdef HAVE_MEDIAPLAYER d->osd->video(true); d->osd->pause(false); setCurrentIndex(view); d->osd->setCurrentUrl(currentItem()); #endif break; } default : // EndView { #ifdef HAVE_MEDIAPLAYER d->videoView->stop(); d->osd->video(false); #endif d->osd->pause(true); setCurrentIndex(view); break; } } } void SlideShowLoader::setCurrentItem(const QUrl& url) { int index = d->settings->indexOf(url); if (index != -1) { d->fileIndex = index - 1; } } QUrl SlideShowLoader::currentItem() const { return d->settings->fileList.value(d->fileIndex); } void SlideShowLoader::setShortCutPrefixes(const QMap& prefixes) { d->shortcutPrefixes = prefixes; } void SlideShowLoader::slotLoadNextItem() { int num = d->settings->count(); if (d->fileIndex == (num - 1)) { if (d->settings->loop) { d->fileIndex = -1; } } d->fileIndex++; qCDebug(DIGIKAM_GENERAL_LOG) << "fileIndex: " << d->fileIndex; if (!d->settings->loop) { d->osd->toolBar()->setEnabledPrev(d->fileIndex > 0); d->osd->toolBar()->setEnabledNext(d->fileIndex < (num - 1)); } if ((d->fileIndex >= 0) && (d->fileIndex < num)) { #ifdef HAVE_MEDIAPLAYER QMimeDatabase mimeDB; if (mimeDB.mimeTypeForFile(currentItem().toLocalFile()).name().startsWith(QLatin1String("video/"))) { d->videoView->setCurrentUrl(currentItem()); return; } #endif d->imageView->setLoadUrl(currentItem()); } else { endOfSlide(); } } void SlideShowLoader::slotLoadPrevItem() { int num = d->settings->count(); if (d->fileIndex == 0) { if (d->settings->loop) { d->fileIndex = num; } } d->fileIndex--; qCDebug(DIGIKAM_GENERAL_LOG) << "fileIndex: " << d->fileIndex; if (!d->settings->loop) { d->osd->toolBar()->setEnabledPrev(d->fileIndex > 0); d->osd->toolBar()->setEnabledNext(d->fileIndex < (num - 1)); } if ((d->fileIndex >= 0) && (d->fileIndex < num)) { #ifdef HAVE_MEDIAPLAYER QMimeDatabase mimeDB; if (mimeDB.mimeTypeForFile(currentItem().toLocalFile()) .name().startsWith(QLatin1String("video/"))) { d->videoView->setCurrentUrl(currentItem()); return; } #endif d->imageView->setLoadUrl(currentItem()); } else { endOfSlide(); } } void SlideShowLoader::slotImageLoaded(bool loaded) { if (loaded) { setCurrentView(ImageView); if (d->fileIndex != -1) { if (!d->osd->isPaused()) { d->osd->pause(false); } preloadNextItem(); } } else { #ifdef HAVE_MEDIAPLAYER // Try to load only GIF Images QMimeDatabase mimeDB; if (mimeDB.mimeTypeForFile(currentItem().toLocalFile()) .name() == QLatin1String("image/gif")) { d->videoView->setCurrentUrl(currentItem()); } #else preloadNextItem(); #endif } d->osd->setLoadingReady(true); } void SlideShowLoader::slotVideoLoaded(bool loaded) { if (loaded) { setCurrentView(VideoView); } else { // Failed to load item setCurrentView(ErrorView); if (d->fileIndex != -1) { if (!d->osd->isPaused()) { d->osd->pause(false); } } } preloadNextItem(); } void SlideShowLoader::slotVideoFinished() { if (d->fileIndex != -1) { d->osd->video(false); slotLoadNextItem(); } } void SlideShowLoader::endOfSlide() { setCurrentView(EndView); d->fileIndex = -1; d->osd->toolBar()->setEnabledPlay(false); d->osd->toolBar()->setEnabledNext(false); d->osd->toolBar()->setEnabledPrev(false); } void SlideShowLoader::preloadNextItem() { int index = d->fileIndex + 1; int num = d->settings->count(); if (index >= num) { if (d->settings->loop) { index = 0; } } if (index < num) { QUrl nextItem = d->settings->fileList.value(index); #ifdef HAVE_MEDIAPLAYER QMimeDatabase mimeDB; if (mimeDB.mimeTypeForFile(nextItem.toLocalFile()) .name().startsWith(QLatin1String("video/"))) { return; } #endif d->imageView->setPreloadUrl(nextItem); } } void SlideShowLoader::wheelEvent(QWheelEvent* e) { d->osd->toolBar()->closeConfigurationDialog(); if (e->angleDelta().y() < 0) { d->osd->pause(true); slotLoadNextItem(); } if (e->angleDelta().y() > 0) { if (d->fileIndex == -1) { // EndView => backward. d->fileIndex = d->settings->count(); } d->osd->pause(true); slotLoadPrevItem(); } } void SlideShowLoader::mousePressEvent(QMouseEvent* e) { d->osd->toolBar()->closeConfigurationDialog(); - if (d->fileIndex == -1) + if (e->button() == Qt::LeftButton) { - // EndView => close Slideshow view. + if (d->fileIndex == -1) + { + // EndView => close Slideshow view. - close(); - } + close(); + + return; + } - if (e->button() == Qt::LeftButton) - { d->osd->pause(true); slotLoadNextItem(); } else if (e->button() == Qt::RightButton) { if (d->fileIndex == -1) { // EndView => backward. - d->fileIndex = d->settings->count() - 1; + d->fileIndex = d->settings->count(); } d->osd->pause(true); slotLoadPrevItem(); } } void SlideShowLoader::keyPressEvent(QKeyEvent* e) { if (!e) { return; } if (e->key() == Qt::Key_F4) { d->osd->toggleProperties(); return; } d->osd->toolBar()->keyPressEvent(e); } bool SlideShowLoader::eventFilter(QObject* obj, QEvent* ev) { if (ev->type() == QEvent::MouseMove) { setCursor(QCursor(Qt::ArrowCursor)); #ifdef HAVE_MEDIAPLAYER d->videoView->showIndicator(true); #endif d->mouseMoveTimer->start(); return false; } // pass the event on to the parent class return QWidget::eventFilter(obj, ev); } void SlideShowLoader::slotMouseMoveTimeOut() { if (!d->osd->isUnderMouse()) { setCursor(QCursor(Qt::BlankCursor)); } #ifdef HAVE_MEDIAPLAYER d->videoView->showIndicator(false); #endif } /** * Inspired from Okular's presentation widget * TODO: Add OSX and Windows support */ void SlideShowLoader::inhibitScreenSaver() { #ifdef HAVE_DBUS QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.ScreenSaver"), QLatin1String("/ScreenSaver"), QLatin1String("org.freedesktop.ScreenSaver"), QLatin1String("Inhibit")); message << QLatin1String("digiKam"); message << i18nc("Reason for inhibiting the screensaver activation, when the presentation mode is active", "Giving a slideshow"); QDBusReply reply = QDBusConnection::sessionBus().call(message); if (reply.isValid()) { d->screenSaverCookie = reply.value(); } #endif } void SlideShowLoader::allowScreenSaver() { #ifdef HAVE_DBUS if (d->screenSaverCookie != -1) { QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.ScreenSaver"), QLatin1String("/ScreenSaver"), QLatin1String("org.freedesktop.ScreenSaver"), QLatin1String("UnInhibit")); message << (uint)d->screenSaverCookie; QDBusConnection::sessionBus().send(message); } #endif } void SlideShowLoader::slotAssignRating(int rating) { DInfoInterface::DInfoMap info; info.insert(QLatin1String("rating"), rating); d->settings->iface->setItemInfo(currentItem(), info); dispatchCurrentInfoChange(currentItem()); } void SlideShowLoader::slotAssignColorLabel(int color) { DInfoInterface::DInfoMap info; info.insert(QLatin1String("colorlabel"), color); d->settings->iface->setItemInfo(currentItem(), info); dispatchCurrentInfoChange(currentItem()); } void SlideShowLoader::slotAssignPickLabel(int pick) { DInfoInterface::DInfoMap info; info.insert(QLatin1String("picklabel"), pick); d->settings->iface->setItemInfo(currentItem(), info); dispatchCurrentInfoChange(currentItem()); } void SlideShowLoader::slotToggleTag(int tag) { DInfoInterface::DInfoMap info; info.insert(QLatin1String("tag"), tag); d->settings->iface->setItemInfo(currentItem(), info); dispatchCurrentInfoChange(currentItem()); } void SlideShowLoader::slotHandleShortcut(const QString& shortcut, int val) { //qCDebug(DIGIKAM_GENERAL_LOG) << "SlideShowLoader::slotHandleShortcut"; if (d->shortcutPrefixes.contains(QLatin1String("rating")) && shortcut.startsWith(d->shortcutPrefixes[QLatin1String("rating")])) { slotAssignRating(val); return; } if (d->shortcutPrefixes.contains(QLatin1String("colorlabel")) && shortcut.startsWith(d->shortcutPrefixes[QLatin1String("colorlabel")])) { slotAssignColorLabel(val); return; } if (d->shortcutPrefixes.contains(QLatin1String("picklabel")) && shortcut.startsWith(d->shortcutPrefixes[QLatin1String("picklabel")])) { slotAssignPickLabel(val); return; } if (d->shortcutPrefixes.contains(QLatin1String("tag")) && shortcut.startsWith(d->shortcutPrefixes[QLatin1String("tag")])) { slotToggleTag(val); return; } qCWarning(DIGIKAM_GENERAL_LOG) << "Shortcut is not yet supported in SlideShowLoader::slotHandleShortcut():" << shortcut; } void SlideShowLoader::dispatchCurrentInfoChange(const QUrl& url) { if (currentItem() == url) { d->osd->setCurrentUrl(currentItem()); } } void SlideShowLoader::slotPause() { #ifdef HAVE_MEDIAPLAYER if (currentIndex() == VideoView) { d->videoView->pause(true); } else #endif { d->osd->pause(true); } } void SlideShowLoader::slotPlay() { d->settings->suffleImages(); #ifdef HAVE_MEDIAPLAYER if (currentIndex() == VideoView) { d->videoView->pause(false); } else #endif { d->osd->pause(false); } } void SlideShowLoader::slotScreenSelected(int screen) { if (screen >= qApp->screens().count()) { return; } QRect deskRect = qApp->screens().at(screen)->geometry(); setWindowState(windowState() & ~Qt::WindowFullScreen); move(deskRect.x(), deskRect.y()); resize(deskRect.width(), deskRect.height()); setWindowState(windowState() | Qt::WindowFullScreen); // update OSD position if (d->fileIndex != -1) { qApp->processEvents(); d->osd->setCurrentUrl(currentItem()); } qCDebug(DIGIKAM_GENERAL_LOG) << "Slideshow: move to screen: " << screen << " :: " << deskRect; } } // namespace DigikamGenericSlideShowPlugin diff --git a/core/dplugins/generic/view/slideshow/slideshowplugin.cpp b/core/dplugins/generic/view/slideshow/slideshowplugin.cpp index 1582ff4c55..0485096366 100644 --- a/core/dplugins/generic/view/slideshow/slideshowplugin.cpp +++ b/core/dplugins/generic/view/slideshow/slideshowplugin.cpp @@ -1,304 +1,291 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2018-07-30 * Description : a plugin to render slideshow. * * Copyright (C) 2018-2020 by Gilles Caulier * Copyright (C) 2019-2020 by Minh Nghia Duong * * 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 "slideshowplugin.h" // Qt includes #include #include #include #include #include // KDE includes #include // Local includes #include "metaenginesettings.h" #include "setupslideshow_dialog.h" #include "slideshowloader.h" using namespace Digikam; namespace DigikamGenericSlideShowPlugin { SlideShowPlugin::SlideShowPlugin(QObject* const parent) : DPluginGeneric(parent) { } SlideShowPlugin::~SlideShowPlugin() { } QString SlideShowPlugin::name() const { return i18n("SlideShow"); } QString SlideShowPlugin::iid() const { return QLatin1String(DPLUGIN_IID); } QIcon SlideShowPlugin::icon() const { return QIcon::fromTheme(QLatin1String("view-presentation")); } QString SlideShowPlugin::description() const { return i18n("A tool to render slideshow"); } QString SlideShowPlugin::details() const { return i18n("

This tool render a series of items as a simple slide-show.

" "

Plenty of items properties can be displayed as overlay while running.

" "

This tool can play album contents in recursive mode with children albums if any.

"); } QList SlideShowPlugin::authors() const { return QList() << DPluginAuthor(QString::fromUtf8("Enrico Ros"), QString::fromUtf8("eros dot kde at email dot it"), QString::fromUtf8("(C) 2004")) << DPluginAuthor(QString::fromUtf8("Renchi Raju"), QString::fromUtf8("renchi dot raju at gmail dot com"), QString::fromUtf8("(C) 2004-2005")) << DPluginAuthor(QString::fromUtf8("Gilles Caulier"), QString::fromUtf8("caulier dot gilles at gmail dot com"), QString::fromUtf8("(C) 2005-2020")) << DPluginAuthor(QString::fromUtf8("Minh Nghia Duong"), QString::fromUtf8("minhnghiaduong997 at gmail dot com"), QString::fromUtf8("(C) 2019-2020")) ; } void SlideShowPlugin::setup(QObject* const parent) { DPluginAction* const ac = new DPluginAction(parent); ac->setIcon(icon()); ac->setText(i18nc("@action", "Slideshow")); ac->setObjectName(QLatin1String("slideshow_plugin")); ac->setActionCategory(DPluginAction::GenericView); DInfoInterface* const iface = infoIface(ac); if (iface && (parent->objectName() == QLatin1String("Digikam"))) { QMenu* const slideShowActions = new QMenu(i18n("Slideshow"), nullptr); slideShowActions->setIcon(icon()); ac->setMenu(slideShowActions); // Action show all QAction* const slideShowAllAction = new QAction(i18n("All"), ac); slideShowAllAction->setObjectName(QLatin1String("slideshow_all")); slideShowAllAction->setShortcut(Qt::Key_F9); slideShowActions->addAction(slideShowAllAction); connect(slideShowAllAction, SIGNAL(triggered()), this, SLOT(slotMenuSlideShowAll())); - connect(slideShowActions->menuAction(), SIGNAL(triggered()), - slideShowAllAction, SLOT(trigger())); - // Action show selection QAction* const slideShowSelectionAction = new QAction(i18n("Selection"), ac); slideShowSelectionAction->setObjectName(QLatin1String("slideshow_selected")); slideShowSelectionAction->setShortcut(Qt::ALT + Qt::Key_F9); slideShowActions->addAction(slideShowSelectionAction); connect(slideShowSelectionAction, SIGNAL(triggered()), this, SLOT(slotMenuSlideShowSelection())); // Action show recursive QAction* const slideShowRecursiveAction = new QAction(i18n("With All Sub-Albums"), ac); slideShowRecursiveAction->setObjectName(QLatin1String("slideshow_recursive")); slideShowRecursiveAction->setShortcut(Qt::SHIFT + Qt::Key_F9); slideShowActions->addAction(slideShowRecursiveAction); connect(slideShowRecursiveAction, SIGNAL(triggered()), this, SLOT(slotMenuSlideShowRecursive())); connect(ac, SIGNAL(triggered(bool)), this, SLOT(slotShowManual())); } else { ac->setShortcut(Qt::Key_F9); connect(ac, SIGNAL(triggered(bool)), this, SLOT(slotMenuSlideShow())); } addAction(ac); } void SlideShowPlugin::addConnectionSlideEnd(QObject* const obj) { Q_UNUSED(obj); } void SlideShowPlugin::slotMenuSlideShow() { QUrl startFrom; DPluginAction* const ac = dynamic_cast(sender()); if (ac) { startFrom = ac->data().toUrl(); ac->setData(QVariant()); } SlideShowSettings* const settings = new SlideShowSettings(); settings->iface = infoIface(ac); settings->readFromConfig(); settings->exifRotate = MetaEngineSettings::instance()->settings().exifRotate; settings->fileList = settings->iface->currentAlbumItems(); slideshow(settings, true, startFrom); } void SlideShowPlugin::slotMenuSlideShowAll() { SlideShowSettings* const settings = new SlideShowSettings(); settings->iface = infoIface(sender()->parent()); settings->readFromConfig(); settings->fileList = settings->iface->currentAlbumItems(); slideshow(settings); } void SlideShowPlugin::slotMenuSlideShowSelection() { SlideShowSettings* const settings = new SlideShowSettings(); settings->iface = infoIface(sender()->parent()); settings->readFromConfig(); settings->fileList = settings->iface->currentSelectedItems(); slideshow(settings); } void SlideShowPlugin::slotMenuSlideShowRecursive() { DInfoInterface* const iface = infoIface(sender()->parent()); connect(iface, SIGNAL(signalAlbumItemsRecursiveCompleted(QList)), this, SLOT(slotShowRecursive(QList))); iface->parseAlbumItemsRecursive(); } void SlideShowPlugin::slotShowRecursive(const QList& imageList) { SlideShowSettings* const settings = new SlideShowSettings(); settings->iface = dynamic_cast(sender()); settings->readFromConfig(); settings->fileList = imageList; slideshow(settings); } void SlideShowPlugin::slotShowManual() { DPluginAction* const ac = dynamic_cast(sender()); - QUrl startFrom; - - if (ac) - { - startFrom = ac->data().toUrl(); - - ac->setData(QVariant()); - - if (!startFrom.isValid()) - { - return; - } - } - else + if (!ac) { return; } + QUrl startFrom(ac->data().toUrl()); + ac->setData(QVariant()); + SlideShowSettings* const settings = new SlideShowSettings(); settings->iface = infoIface(ac); settings->readFromConfig(); settings->exifRotate = MetaEngineSettings::instance()->settings().exifRotate; settings->fileList = settings->iface->currentAlbumItems(); - slideshow(settings, false, startFrom); + slideshow(settings, !startFrom.isValid(), startFrom); } void SlideShowPlugin::slideshow(SlideShowSettings* const settings, bool autoPlayEnabled, const QUrl& startFrom) { settings->autoPlayEnabled = autoPlayEnabled; settings->plugin = this; // TODO: preview settings for digikam //settings.previewSettings = ApplicationSettings::instance()->getPreviewSettings(); if (startFrom.isValid()) { settings->imageUrl = startFrom; } SlideShowLoader* const slide = new SlideShowLoader(settings); slide->setShortCutPrefixes(settings->iface->passShortcutActionsToWidget(slide)); if (settings->imageUrl.isValid()) { slide->setCurrentItem(settings->imageUrl); } else if (settings->startWithCurrent) { if (!settings->iface->currentSelectedItems().isEmpty()) { slide->setCurrentItem(settings->iface->currentSelectedItems()[0]); } else { // no current selection, do nothing. return; } } connect(slide, SIGNAL(signalLastItemUrl(QUrl)), settings->iface, SIGNAL(signalLastItemUrl(QUrl))); connect(settings->iface, SIGNAL(signalShortcutPressed(QString,int)), slide, SLOT(slotHandleShortcut(QString,int))); slide->show(); } } // namespace DigikamGenericSlideShowPlugin diff --git a/core/dplugins/generic/view/slideshow/widgets/slideosd.cpp b/core/dplugins/generic/view/slideshow/widgets/slideosd.cpp index 8b6587e5b1..dafce9ff70 100644 --- a/core/dplugins/generic/view/slideshow/widgets/slideosd.cpp +++ b/core/dplugins/generic/view/slideshow/widgets/slideosd.cpp @@ -1,425 +1,411 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2014-09-18 * Description : slideshow OSD widget * * Copyright (C) 2014-2020 by Gilles Caulier * Copyright (C) 2019-2020 by Minh Nghia Duong * * 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 "slideosd.h" // Qt includes #include #include #include -#include -#include #include #include #include -// Windows includes - -#ifdef Q_OS_WIN -# include -#endif - // Local includes #include "digikam_debug.h" #include "slideshowloader.h" #include "slidetoolbar.h" #include "slideproperties.h" #include "ratingwidget.h" #include "colorlabelwidget.h" #include "picklabelwidget.h" #include "dinfointerface.h" using namespace Digikam; namespace DigikamGenericSlideShowPlugin { class Q_DECL_HIDDEN SlideOSD::Private { public: explicit Private() : paused(false), video(false), blink(false), ready(false), refresh(1000), ///< Progress bar refresh in ms progressBar(nullptr), progressTimer(nullptr), labelsBox(nullptr), progressBox(nullptr), parent(nullptr), slideProps(nullptr), toolBar(nullptr), ratingWidget(nullptr), clWidget(nullptr), plWidget(nullptr), settings(nullptr) { } bool paused; bool video; bool blink; bool ready; int const refresh; QProgressBar* progressBar; QTimer* progressTimer; DHBox* labelsBox; DHBox* progressBox; SlideShowLoader* parent; SlideProperties* slideProps; SlideToolBar* toolBar; RatingWidget* ratingWidget; ColorLabelSelector* clWidget; PickLabelSelector* plWidget; SlideShowSettings* settings; }; SlideOSD::SlideOSD(SlideShowSettings* const settings, SlideShowLoader* const parent) : QWidget(parent), d(new Private()) { Qt::WindowFlags flags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint; setWindowFlags(flags); setAttribute(Qt::WA_TranslucentBackground, true); setAttribute(Qt::WA_ShowWithoutActivating, true); setMouseTracking(true); - d->settings = settings; d->parent = parent; + d->settings = settings; d->slideProps = new SlideProperties(d->settings, this); d->slideProps->installEventFilter(d->parent); // --------------------------------------------------------------- d->labelsBox = new DHBox(this); d->clWidget = new ColorLabelSelector(d->labelsBox); d->clWidget->installEventFilter(this); d->clWidget->installEventFilter(d->parent); d->clWidget->colorLabelWidget()->installEventFilter(this); d->clWidget->setFocusPolicy(Qt::NoFocus); d->clWidget->setMouseTracking(true); d->plWidget = new PickLabelSelector(d->labelsBox); d->plWidget->installEventFilter(this); d->plWidget->installEventFilter(d->parent); d->plWidget->setFocusPolicy(Qt::NoFocus); d->plWidget->pickLabelWidget()->installEventFilter(this); d->plWidget->setMouseTracking(true); d->ratingWidget = new RatingWidget(d->labelsBox); d->ratingWidget->setTracking(false); d->ratingWidget->setFading(false); d->ratingWidget->installEventFilter(this); d->ratingWidget->installEventFilter(d->parent); d->ratingWidget->setFocusPolicy(Qt::NoFocus); d->ratingWidget->setMouseTracking(true); d->labelsBox->layout()->setAlignment(d->ratingWidget, Qt::AlignVCenter | Qt::AlignLeft); d->labelsBox->installEventFilter(d->parent); d->labelsBox->setMouseTracking(true); d->labelsBox->setVisible(d->settings->printLabels || d->settings->printRating); d->ratingWidget->setVisible(d->settings->printRating); d->clWidget->setVisible(d->settings->printLabels); d->plWidget->setVisible(d->settings->printLabels); connect(d->ratingWidget, SIGNAL(signalRatingChanged(int)), parent, SLOT(slotAssignRating(int))); connect(d->clWidget, SIGNAL(signalColorLabelChanged(int)), parent, SLOT(slotAssignColorLabel(int))); connect(d->plWidget, SIGNAL(signalPickLabelChanged(int)), parent, SLOT(slotAssignPickLabel(int))); // --------------------------------------------------------------- d->progressBox = new DHBox(this); d->progressBox->setVisible(d->settings->showProgressIndicator); d->progressBox->installEventFilter(d->parent); d->progressBox->setMouseTracking(true); d->progressBar = new QProgressBar(d->progressBox); d->progressBar->setMinimum(0); d->progressBar->setMaximum(d->settings->delay); d->progressBar->setFocusPolicy(Qt::NoFocus); d->progressBar->installEventFilter(d->parent); d->progressBar->setMouseTracking(true); d->toolBar = new SlideToolBar(d->settings, d->progressBox); d->toolBar->installEventFilter(this); d->toolBar->installEventFilter(d->parent); connect(d->toolBar, SIGNAL(signalPause()), d->parent, SLOT(slotPause())); connect(d->toolBar, SIGNAL(signalPlay()), d->parent, SLOT(slotPlay())); connect(d->toolBar, SIGNAL(signalPlay()), this, SLOT(slotRechargeSettings())); connect(d->toolBar, SIGNAL(signalNext()), d->parent, SLOT(slotLoadNextItem())); connect(d->toolBar, SIGNAL(signalPrev()), d->parent, SLOT(slotLoadPrevItem())); connect(d->toolBar, SIGNAL(signalClose()), d->parent, SLOT(close())); connect(d->toolBar, SIGNAL(signalScreenSelected(int)), d->parent, SLOT(slotScreenSelected(int))); // --------------------------------------------------------------- QGridLayout* const grid = new QGridLayout(this); grid->addWidget(d->slideProps, 1, 0, 1, 2); grid->addWidget(d->labelsBox, 2, 0, 1, 1); grid->addWidget(d->progressBox, 3, 0, 1, 1); grid->setRowStretch(0, 10); grid->setColumnStretch(1, 10); grid->setContentsMargins(QMargins()); grid->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); // --------------------------------------------------------------- d->progressTimer = new QTimer(this); d->progressTimer->setSingleShot(false); connect(d->progressTimer, SIGNAL(timeout()), this, SLOT(slotProgressTimer())); QTimer::singleShot(500, this, SLOT(slotStart())); } SlideOSD::~SlideOSD() { d->progressTimer->stop(); delete d; } void SlideOSD::slotStart() { d->parent->slotLoadNextItem(); d->progressTimer->start(d->refresh); pause(!d->settings->autoPlayEnabled); } void SlideOSD::slotRechargeSettings() { d->labelsBox->setVisible(d->settings->printLabels || d->settings->printRating); d->ratingWidget->setVisible(d->settings->printRating); d->clWidget->setVisible(d->settings->printLabels); d->plWidget->setVisible(d->settings->printLabels); d->progressBox->setVisible(d->settings->showProgressIndicator); d->progressBar->setMaximum(d->settings->delay); } SlideToolBar* SlideOSD::toolBar() const { return d->toolBar; } void SlideOSD::setCurrentUrl(const QUrl& url) { DInfoInterface::DInfoMap info = d->settings->iface->itemInfo(url); DItemInfo item(info); // Update info text. d->slideProps->setCurrentUrl(url); // Display Labels. if (d->settings->printLabels) { d->clWidget->blockSignals(true); d->plWidget->blockSignals(true); d->clWidget->setColorLabel((ColorLabel)item.colorLabel()); d->plWidget->setPickLabel((PickLabel)item.pickLabel()); d->clWidget->blockSignals(false); d->plWidget->blockSignals(false); } if (d->settings->printRating) { d->ratingWidget->blockSignals(true); d->ratingWidget->setRating(item.rating()); d->ratingWidget->blockSignals(false); } // Make the OSD the proper size layout()->activate(); resize(sizeHint()); - QScreen* screen = qApp->primaryScreen(); - - if (QWidget* const widget = nativeParentWidget()) - { - if (QWindow* const window = widget->windowHandle()) - { - screen = window->screen(); - } - } - - QRect geometry(screen->availableGeometry()); - move(10, geometry.bottom() - height()); + move(10, d->parent->geometry().bottom() - height()); show(); raise(); } +QSize SlideOSD::slideShowSize() const +{ + return d->parent->size(); +} + bool SlideOSD::eventFilter(QObject* obj, QEvent* ev) { if ( (obj == d->labelsBox) || (obj == d->ratingWidget) || (obj == d->clWidget) || (obj == d->plWidget) || (obj == d->clWidget->colorLabelWidget()) || (obj == d->plWidget->pickLabelWidget()) ) { if (ev->type() == QEvent::Enter) { d->paused = isPaused(); d->parent->slotPause(); return false; } if (ev->type() == QEvent::Leave) { if (!d->paused) { d->parent->slotPlay(); } return false; } } // pass the event on to the parent class return QWidget::eventFilter(obj, ev); } void SlideOSD::slotProgressTimer() { QString str = QString::fromUtf8("(%1/%2)") .arg(QString::number(d->settings->fileList.indexOf(d->parent->currentItem()) + 1)) .arg(QString::number(d->settings->fileList.count())); if (isPaused()) { d->blink = !d->blink; if (d->blink) { str = QString(); } d->progressBar->setFormat(str); } else if (d->video) { d->progressBar->setFormat(str); return; } else { d->progressBar->setFormat(str); if (d->progressBar->value() == d->settings->delay) { if (!d->ready) { return; } d->ready = false; d->parent->slotLoadNextItem(); } d->progressBar->setValue(d->progressBar->value()+1); } } void SlideOSD::pause(bool b) { d->toolBar->pause(b); if (!b) { d->progressBar->setValue(0); } } void SlideOSD::video(bool b) { d->video = b; } bool SlideOSD::isPaused() const { return d->toolBar->isPaused(); } bool SlideOSD::isUnderMouse() const { return ( d->ratingWidget->underMouse() || d->progressBar->underMouse() || d->clWidget->underMouse() || d->plWidget->underMouse() || d->toolBar->underMouse() ); } void SlideOSD::toggleProperties() { d->slideProps->togglePaintEnabled(); } void SlideOSD::setLoadingReady(bool b) { d->ready = b; } } // namespace DigikamGenericSlideShowPlugin diff --git a/core/dplugins/generic/view/slideshow/widgets/slideosd.h b/core/dplugins/generic/view/slideshow/widgets/slideosd.h index b28dea3f89..2f84d463b2 100644 --- a/core/dplugins/generic/view/slideshow/widgets/slideosd.h +++ b/core/dplugins/generic/view/slideshow/widgets/slideosd.h @@ -1,84 +1,85 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2014-09-18 * Description : slideshow OSD widget * * Copyright (C) 2014-2020 by Gilles Caulier * Copyright (C) 2019-2020 by Minh Nghia Duong * * 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_SLIDE_OSD_PLUGIN_H #define DIGIKAM_SLIDE_OSD_PLUGIN_H // Qt includes #include #include // Local includes #include "slideshowsettings.h" class QEvent; namespace DigikamGenericSlideShowPlugin { class SlideShowLoader; class SlideToolBar; class SlideOSD : public QWidget { Q_OBJECT public: - explicit SlideOSD(SlideShowSettings* const settings, SlideShowLoader* const parent = nullptr); + explicit SlideOSD(SlideShowSettings* const settings, SlideShowLoader* const parent); ~SlideOSD(); void setCurrentUrl(const QUrl& url); void pause(bool b); void video(bool b); bool isPaused() const; bool isUnderMouse() const; void toggleProperties(); void setLoadingReady(bool b); SlideToolBar* toolBar() const; + QSize slideShowSize() const; private Q_SLOTS: void slotProgressTimer(); void slotStart(); void slotRechargeSettings(); private: bool eventFilter(QObject* obj, QEvent* ev) override; private: class Private; Private* const d; }; } // namespace DigikamGenericSlideShowPlugin #endif // DIGIKAM_SLIDE_OSD_PLUGIN_H diff --git a/core/dplugins/generic/view/slideshow/widgets/slideproperties.cpp b/core/dplugins/generic/view/slideshow/widgets/slideproperties.cpp index b059fa44d8..b575254dbc 100644 --- a/core/dplugins/generic/view/slideshow/widgets/slideproperties.cpp +++ b/core/dplugins/generic/view/slideshow/widgets/slideproperties.cpp @@ -1,399 +1,393 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2014-09-19 * Description : slide properties widget * * Copyright (C) 2014-2020 by Gilles Caulier * Copyright (C) 2019-2020 by Minh Nghia Duong * * 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 "slideproperties.h" // Qt includes #include #include #include #include #include #include -#include -#include // KDE includes #include // Local includes +#include "slideosd.h" #include "digikam_debug.h" #include "dinfointerface.h" #include "itempropertiestab.h" using namespace Digikam; namespace DigikamGenericSlideShowPlugin { class Q_DECL_HIDDEN SlideProperties::Private { public: explicit Private() : maxStringLen(80), paintEnabled(true), - settings(nullptr) + settings(nullptr), + parent(nullptr) { } const int maxStringLen; bool paintEnabled; QUrl url; SlideShowSettings* settings; + SlideOSD* parent; DInfoInterface::DInfoMap infoMap; }; -SlideProperties::SlideProperties(SlideShowSettings* const settings, QWidget* const parent) +SlideProperties::SlideProperties(SlideShowSettings* const settings, SlideOSD* const parent) : QWidget(parent), d(new Private) { + d->parent = parent; d->settings = settings; setMouseTracking(true); } SlideProperties::~SlideProperties() { delete d; } void SlideProperties::setCurrentUrl(const QUrl& url) { - QScreen* screen = qApp->primaryScreen(); - - if (QWidget* const widget = nativeParentWidget()) - { - if (QWindow* const window = widget->windowHandle()) - { - screen = window->screen(); - } - } - - setFixedSize(screen->availableGeometry().size() / 1.5); d->infoMap = d->settings->iface->itemInfo(url); d->url = url; + move(0, 0); + setFixedSize(d->parent->slideShowSize()); + update(); } void SlideProperties::paintEvent(QPaintEvent*) { if (!d->paintEnabled) { return; } QPainter p(this); p.setFont(d->settings->captionFont); DItemInfo item(d->infoMap); QString str; /* PhotoInfoContainer photoInfo = d->info.photoInfo; */ QString comment = item.comment(); QString title = item.title(); QStringList tags = item.keywords(); int offset = 0; // Display tag names. if (d->settings->printTags) { printTags(p, offset, tags); } // Display Titles. if (d->settings->printTitle) { str.clear(); if (!title.isEmpty()) { str += title; printInfoText(p, offset, str); } } // Display Captions if no Titles. if (d->settings->printCapIfNoTitle) { str.clear(); if (title.isEmpty()) { str += comment; printComments(p, offset, str); } } // Display Comments. if (d->settings->printComment) { str = comment; printComments(p, offset, str); } // Display Make and Model. if (d->settings->printMakeModel) { str.clear(); QString make = item.make(); QString model = item.model(); if (!make.isEmpty()) { ItemPropertiesTab::shortenedMakeInfo(make); str = make; } if (!model.isEmpty()) { if (!make.isEmpty()) { str += QLatin1String(" / "); } ItemPropertiesTab::shortenedModelInfo(model); str += model; } printInfoText(p, offset, str); } // Display Exposure and Sensitivity. if (d->settings->printExpoSensitivity) { str.clear(); QString exposureTime = item.exposureTime(); QString sensitivity = item.sensitivity(); if (!exposureTime.isEmpty()) { str = exposureTime; } if (!sensitivity.isEmpty()) { if (!exposureTime.isEmpty()) { str += QLatin1String(" / "); } str += i18n("%1 ISO", sensitivity); } printInfoText(p, offset, str); } // Display Aperture and Focal. if (d->settings->printApertureFocal) { str.clear(); QString aperture = item.aperture(); QString focalLength = item.focalLength(); QString focalLength35mm = item.focalLength35mm(); if (!aperture.isEmpty()) { str = aperture; } if (focalLength35mm.isEmpty()) { if (!focalLength.isEmpty()) { if (!aperture.isEmpty()) { str += QLatin1String(" / "); } str += focalLength; } } else { if (!aperture.isEmpty()) { str += QLatin1String(" / "); } if (!focalLength.isEmpty()) { str += QString::fromUtf8("%1 (%2)").arg(focalLength).arg(focalLength35mm); } else { str += QString::fromUtf8("%1").arg(focalLength35mm); } } printInfoText(p, offset, str); } // Display Creation Date. if (d->settings->printDate) { QDateTime dateTime = item.dateTime(); if (dateTime.isValid()) { str = QLocale().toString(dateTime, QLocale::ShortFormat); printInfoText(p, offset, str); } } // Display image File Name. if (d->settings->printName) { printInfoText(p, offset, d->url.fileName()); } } void SlideProperties::printInfoText(QPainter& p, int& offset, const QString& str, const QColor& pcol) { if (!str.isEmpty()) { offset += QFontMetrics(p.font()).lineSpacing(); p.setPen(Qt::black); for (int x = -1 ; x <= 1 ; ++x) { for (int y = offset + 1 ; y >= offset - 1 ; --y) { p.drawText(x, p.window().height() - y, str); } } p.setPen(pcol); p.drawText(0, p.window().height() - offset, str); } } void SlideProperties::printComments(QPainter& p, int& offset, const QString& comments) { QStringList commentsByLines; uint commentsIndex = 0; // Comments QString index while (commentsIndex < (uint)comments.length()) { QString newLine; bool breakLine = false; // End Of Line found uint currIndex; // Comments QString current index // Check minimal lines dimension uint commentsLinesLengthLocal = d->maxStringLen; for (currIndex = commentsIndex ; (currIndex < (uint)comments.length()) && !breakLine ; ++currIndex) { if ((comments.at(currIndex) == QLatin1Char('\n')) || comments.at(currIndex).isSpace()) { breakLine = true; } } if (commentsLinesLengthLocal <= (currIndex - commentsIndex)) { commentsLinesLengthLocal = (currIndex - commentsIndex); } breakLine = false; for (currIndex = commentsIndex ; (currIndex <= (commentsIndex + commentsLinesLengthLocal)) && (currIndex < (uint)comments.length()) && !breakLine ; ++currIndex) { breakLine = (comments.at(currIndex) == QLatin1Char('\n')) ? true : false; if (breakLine) { newLine.append(QLatin1Char(' ')); } else { newLine.append(comments.at(currIndex)); } } commentsIndex = currIndex; // The line is ended if (commentsIndex != (uint)comments.length()) { while (!newLine.endsWith(QLatin1Char(' '))) { newLine.truncate(newLine.length() - 1); --commentsIndex; } } commentsByLines.prepend(newLine.trimmed()); } for (int i = 0 ; i < (int)commentsByLines.count() ; ++i) { printInfoText(p, offset, commentsByLines.at(i)); } } void SlideProperties::printTags(QPainter& p, int& offset, QStringList& tags) { tags.sort(); QString str = tags.join(QLatin1String(", ")); if (!str.isEmpty()) { printInfoText(p, offset, str, qApp->palette().color(QPalette::Link).name()); } } void SlideProperties::togglePaintEnabled() { d->paintEnabled = !d->paintEnabled; update(); } } // namespace DigikamGenericSlideShowPlugin diff --git a/core/dplugins/generic/view/slideshow/widgets/slideproperties.h b/core/dplugins/generic/view/slideshow/widgets/slideproperties.h index 01d70a4e86..a65254cea1 100644 --- a/core/dplugins/generic/view/slideshow/widgets/slideproperties.h +++ b/core/dplugins/generic/view/slideshow/widgets/slideproperties.h @@ -1,66 +1,68 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2014-09-19 * Description : slide properties widget * * Copyright (C) 2014-2020 by Gilles Caulier * Copyright (C) 2019-2020 by Minh Nghia Duong * * 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_SLIDE_PROPERTIES_PLUGIN_H #define DIGIKAM_SLIDE_PROPERTIES_PLUGIN_H #include #include #include #include #include // Local includes #include "slideshowsettings.h" namespace DigikamGenericSlideShowPlugin { +class SlideOSD; + class SlideProperties : public QWidget { public: - explicit SlideProperties(SlideShowSettings* const settings, QWidget* const parent); + explicit SlideProperties(SlideShowSettings* const settings, SlideOSD* const parent); ~SlideProperties(); void setCurrentUrl(const QUrl& url); void togglePaintEnabled(); private: void printInfoText(QPainter& p, int& offset, const QString& str, const QColor& pcol=Qt::white); void printComments(QPainter& p, int& offset, const QString& comments); void printTags(QPainter& p, int& offset, QStringList& tags); void paintEvent(QPaintEvent*) override; private: class Private; Private* const d; }; } // namespace DigikamGenericSlideShowPlugin #endif // DIGIKAM_SLIDE_PROPERTIES_PLUGIN_H diff --git a/core/dplugins/generic/webservices/vkontakte/backend/vkontakte_authenticationdialog.cpp b/core/dplugins/generic/webservices/vkontakte/backend/vkontakte_authenticationdialog.cpp index 2ce707863f..4b2f67cba4 100644 --- a/core/dplugins/generic/webservices/vkontakte/backend/vkontakte_authenticationdialog.cpp +++ b/core/dplugins/generic/webservices/vkontakte/backend/vkontakte_authenticationdialog.cpp @@ -1,247 +1,248 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-02-19 * Description : a tool to export images to VKontakte web service * * Copyright (C) 2010 by Thomas McGuire * Copyright (C) 2011-2015 by Alexander Potashev * Copyright (C) 2011-2020 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 "vkontakte_authenticationdialog.h" #include "digikam_config.h" // Qt includes #ifdef HAVE_QWEBENGINE # include # include # include # include #else # include #endif #include #include #include #include #include #include #include +#include // KDE incudes #include // Local includes #include "digikam_debug.h" #include "vkontakte_util.h" namespace Vkontakte { class Q_DECL_HIDDEN AuthenticationDialog::Private { public: QString appId; Vkontakte::AppPermissions::Value permissions; QString displayMode; #ifdef HAVE_QWEBENGINE QWebEngineView* webView; #else QWebView* webView; #endif QProgressBar* progressBar; QString error; QString errorDescription; }; AuthenticationDialog::AuthenticationDialog(QWidget* const parent) : QDialog(parent), d(new Private) { d->displayMode = QLatin1String("page"); setWindowTitle(i18nc("@title:window", "Authenticate with VKontakte")); setAttribute(Qt::WA_DeleteOnClose, true); QWidget* const progressWidget = new QWidget(this); QHBoxLayout* const progressLayout = new QHBoxLayout(progressWidget); progressLayout->setContentsMargins(QMargins()); #ifdef HAVE_QWEBENGINE d->webView = new QWebEngineView(this); d->webView->page()->profile()->cookieStore()->deleteAllCookies(); #else d->webView = new QWebView(this); d->webView->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); d->webView->page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar()); #endif d->progressBar = new QProgressBar(this); d->progressBar->setRange(0, 100); QLabel* const progressLabel = new QLabel(i18n("Loading Page:"), this); progressLayout->addWidget(progressLabel); progressLayout->addWidget(d->progressBar); // Buttons QDialogButtonBox* const buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, this); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); // Layout QVBoxLayout* const layout = new QVBoxLayout(this); layout->addWidget(progressWidget); layout->addWidget(d->webView); layout->addWidget(buttonBox); setLayout(layout); connect(buttonBox, &QDialogButtonBox::rejected, this, &AuthenticationDialog::canceled); connect(d->webView, SIGNAL(urlChanged(QUrl)), this, SLOT(urlChanged(QUrl))); connect(d->webView, SIGNAL(loadStarted()), progressWidget, SLOT(show())); connect(d->webView, SIGNAL(loadFinished(bool)), progressWidget, SLOT(hide())); connect(d->webView, SIGNAL(loadProgress(int)), d->progressBar, SLOT(setValue(int))); connect(d->webView, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool))); } AuthenticationDialog::~AuthenticationDialog() { delete d; } void AuthenticationDialog::setAppId(const QString& appId) { d->appId = appId; } void Vkontakte::AuthenticationDialog::setPermissions(Vkontakte::AppPermissions::Value permissions) { d->permissions = permissions; } // display= {page, popup, touch, wap} void AuthenticationDialog::setDisplayMode(const QString& displayMode) { d->displayMode = displayMode; } void AuthenticationDialog::start() { Q_ASSERT(!d->appId.isEmpty()); const QString url = QString::fromUtf8( "http://oauth.vk.com/authorize?" "client_id=%1&" "scope=%2&" "redirect_uri=http://oauth.vk.com/blank.html&" "display=%3&" "response_type=token") .arg(d->appId) .arg(appPermissionsToStringList(d->permissions).join(QLatin1String(","))) .arg(d->displayMode); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Showing" << url; d->webView->setUrl(QUrl::fromUserInput(url)); show(); } void AuthenticationDialog::showErrorDialog() { hide(); const QString details = i18n("VKontakte Error Description: %1
" "VKontakte Error: %2
", d->errorDescription, d->error); QMessageBox::warning(this, i18n("Authentication with VKontakte was not successful."), details, i18nc("@title:window", "Authentication Problem")); emit canceled(); close(); } void AuthenticationDialog::urlChanged(const QUrl& url) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Navigating to" << url; if ((url.host() == QLatin1String("oauth.vk.com")) && (url.path() == QLatin1String("/blank.html"))) { const QUrlQuery query(url); d->error = query.queryItemValue(QLatin1String("error")); d->errorDescription = query.queryItemValue(QLatin1String("error_description")).replace(QLatin1Char('+'), QLatin1Char(' ')); if (!d->error.isEmpty() || !d->errorDescription.isEmpty()) { QTimer::singleShot(0, this, SLOT(showErrorDialog())); return; } // The URL comes in the form "bla#access_token=bla&expires_in=foo", we need to convert from // # to ? const QUrl fixedURL = QUrl::fromUserInput(url.toString().replace(QLatin1Char('#'), QLatin1Char('?'))); const QUrlQuery fixedQuery(fixedURL); const QString accessToken = fixedQuery.queryItemValue(QLatin1String("access_token")); const QString tokenExpiresIn = fixedQuery.queryItemValue(QLatin1String("expires_in")); // TODO: use this for something? if (!accessToken.isEmpty()) { emit authenticated(accessToken); QTimer::singleShot(0, this, SLOT(close())); } } } void AuthenticationDialog::loadFinished(bool ok) { if (!ok) { hide(); QMessageBox::critical(parentWidget(), i18n("There was a network error when trying to authenticate with VKontakte web service."), i18nc("@title:window", "Network Error")); emit canceled(); close(); } } } // namespace Vkontakte diff --git a/core/libs/database/utils/ifaces/dio.cpp b/core/libs/database/utils/ifaces/dio.cpp index 0419403a3b..2dd0b1ada5 100644 --- a/core/libs/database/utils/ifaces/dio.cpp +++ b/core/libs/database/utils/ifaces/dio.cpp @@ -1,642 +1,642 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-17 * Description : low level files management interface. * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2012-2013 by Marcel Wiesweg * Copyright (C) 2015 by Mohamed_Anwer * Copyright (C) 2018 by Maik Qualmann * * 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 "dio.h" // Qt includes #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "iteminfo.h" #include "diofinders.h" #include "albummanager.h" #include "tagscache.h" #include "coredb.h" #include "coredbaccess.h" #include "album.h" #include "dmetadata.h" #include "metaenginesettings.h" #include "scancontroller.h" #include "thumbsdb.h" #include "thumbsdbaccess.h" #include "iojobsmanager.h" #include "collectionmanager.h" #include "dnotificationwrapper.h" #include "loadingcacheinterface.h" #include "progressmanager.h" #include "digikamapp.h" #include "iojobdata.h" namespace Digikam { class Q_DECL_HIDDEN DIOCreator { public: DIO object; }; Q_GLOBAL_STATIC(DIOCreator, creator) // ------------------------------------------------------------------------------------------------ DIO* DIO::instance() { return &creator->object; } DIO::DIO() { m_processingCount = 0; } DIO::~DIO() { } void DIO::cleanUp() { } bool DIO::itemsUnderProcessing() { return instance()->m_processingCount; } // Album -> Album ----------------------------------------------------- void DIO::copy(PAlbum* const src, PAlbum* const dest) { if (!src || !dest) { return; } instance()->processJob(new IOJobData(IOJobData::CopyAlbum, src, dest)); } void DIO::move(PAlbum* const src, PAlbum* const dest) { if (!src || !dest) { return; } #ifdef Q_OS_WIN AlbumManager::instance()->removeWatchedPAlbums(src); #endif instance()->processJob(new IOJobData(IOJobData::MoveAlbum, src, dest)); } // Images -> Album ---------------------------------------------------- void DIO::copy(const QList& infos, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::CopyImage, infos, dest)); } void DIO::move(const QList& infos, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::MoveImage, infos, dest)); } // External files -> album -------------------------------------------- void DIO::copy(const QUrl& src, PAlbum* const dest) { copy(QList() << src, dest); } void DIO::copy(const QList& srcList, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::CopyFiles, srcList, dest)); } void DIO::move(const QUrl& src, PAlbum* const dest) { move(QList() << src, dest); } void DIO::move(const QList& srcList, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::MoveFiles, srcList, dest)); } // Rename -------------------------------------------------------------- void DIO::rename(const QUrl& src, const QString& newName, bool overwrite) { if (src.isEmpty() || newName.isEmpty()) { return; } ItemInfo info = ItemInfo::fromUrl(src); instance()->processJob(new IOJobData(IOJobData::Rename, info, newName, overwrite)); } // Delete -------------------------------------------------------------- void DIO::del(const QList& infos, bool useTrash) { instance()->processJob(new IOJobData(useTrash ? IOJobData::Trash : IOJobData::Delete, infos)); } void DIO::del(const ItemInfo& info, bool useTrash) { del(QList() << info, useTrash); } void DIO::del(PAlbum* const album, bool useTrash) { if (!album) { return; } #ifdef Q_OS_WIN AlbumManager::instance()->removeWatchedPAlbums(album); #endif instance()->createJob(new IOJobData(useTrash ? IOJobData::Trash : IOJobData::Delete, album)); } // Restore Trash ------------------------------------------------------- void DIO::restoreTrash(const DTrashItemInfoList& infos) { instance()->createJob(new IOJobData(IOJobData::Restore, infos)); } // Empty Trash --------------------------------------------------------- void DIO::emptyTrash(const DTrashItemInfoList& infos) { instance()->createJob(new IOJobData(IOJobData::Empty, infos)); } // ------------------------------------------------------------------------------------------------ void DIO::processJob(IOJobData* const data) { const int operation = data->operation(); if (operation == IOJobData::CopyImage || operation == IOJobData::MoveImage) { // this is a fast db operation, do here GroupedImagesFinder finder(data->itemInfos()); data->setItemInfos(finder.infos); QStringList filenames; QList ids; foreach (const ItemInfo& info, data->itemInfos()) { filenames << info.name(); ids << info.id(); } ScanController::instance()->hintAtMoveOrCopyOfItems(ids, data->destAlbum(), filenames); } else if (operation == IOJobData::CopyAlbum || operation == IOJobData::MoveAlbum) { ScanController::instance()->hintAtMoveOrCopyOfAlbum(data->srcAlbum(), data->destAlbum()); createJob(data); return; } else if (operation == IOJobData::Delete || operation == IOJobData::Trash) { qCDebug(DIGIKAM_DATABASE_LOG) << "Number of files to be deleted:" << data->sourceUrls().count(); } SidecarFinder finder(data->sourceUrls()); data->setSourceUrls(finder.localFiles); if (operation == IOJobData::Rename) { if (!data->itemInfos().isEmpty()) { ItemInfo info = data->itemInfos().first(); PAlbum* const album = AlbumManager::instance()->findPAlbum(info.albumId()); if (album) { ScanController::instance()->hintAtMoveOrCopyOfItem(info.id(), album, data->destUrl().fileName()); } for (int i = 0 ; i < finder.localFiles.length() ; ++i) { if (finder.localFileModes.at(i)) { data->setDestUrl(finder.localFiles.at(i), QUrl::fromLocalFile(data->destUrl().toLocalFile() + finder.localFileSuffixes.at(i))); } else { - QFileInfo info(data->destUrl().toLocalFile()); + QFileInfo basInfo(data->destUrl().toLocalFile()); data->setDestUrl(finder.localFiles.at(i), - QUrl::fromLocalFile(info.path() + - QLatin1Char('/') + - info.completeBaseName() + + QUrl::fromLocalFile(basInfo.path() + + QLatin1Char('/') + + basInfo.completeBaseName() + finder.localFileSuffixes.at(i))); } } } } createJob(data); } void DIO::createJob(IOJobData* const data) { if (data->sourceUrls().isEmpty()) { delete data; return; } ProgressItem* item = nullptr; QString itemString = getItemString(data); if (!itemString.isEmpty()) { item = ProgressManager::instance()->createProgressItem(itemString, QString(), true, false); item->setTotalItems(data->sourceUrls().count()); data->setProgressId(item->id()); } IOJobsThread* const jobThread = IOJobsManager::instance()->startIOJobs(data); connect(jobThread, SIGNAL(signalOneProccessed(QUrl)), this, SLOT(slotOneProccessed(QUrl))); connect(jobThread, SIGNAL(finished()), this, SLOT(slotResult())); if (data->operation() == IOJobData::Rename) { connect(jobThread, SIGNAL(signalRenameFailed(QUrl)), this, SIGNAL(signalRenameFailed(QUrl))); connect(jobThread, SIGNAL(finished()), this, SIGNAL(signalRenameFinished())); } if (data->operation() == IOJobData::Empty || data->operation() == IOJobData::Restore) { connect(jobThread, SIGNAL(finished()), this, SIGNAL(signalTrashFinished())); } if (item) { connect(item, SIGNAL(progressItemCanceled(ProgressItem*)), jobThread, SLOT(slotCancel())); connect(item, SIGNAL(progressItemCanceled(ProgressItem*)), this, SLOT(slotCancel(ProgressItem*))); } ++m_processingCount; } void DIO::slotResult() { IOJobsThread* const jobThread = dynamic_cast(sender()); if (!jobThread || !jobThread->jobData()) { return; } IOJobData* const data = jobThread->jobData(); if (jobThread->hasErrors() && data->operation() != IOJobData::Rename) { // Pop-up a message about the error. QString errors = jobThread->errorsList().join(QLatin1Char('\n')); DNotificationWrapper(QString(), errors, DigikamApp::instance(), DigikamApp::instance()->windowTitle()); } if (m_processingCount) { --m_processingCount; } slotCancel(getProgressItem(data)); } void DIO::slotOneProccessed(const QUrl& url) { IOJobsThread* const jobThread = dynamic_cast(sender()); if (!jobThread || !jobThread->jobData()) { return; } IOJobData* const data = jobThread->jobData(); const int operation = data->operation(); if (operation == IOJobData::MoveImage) { ItemInfo info = data->findItemInfo(url); if (!info.isNull() && data->destAlbum()) { CoreDbAccess().db()->moveItem(info.albumId(), info.name(), data->destAlbum()->id(), info.name()); } } else if (operation == IOJobData::Delete) { // Mark the images as obsolete and remove them // from their album and from the grouped PAlbum* const album = data->srcAlbum(); if (album && album->fileUrl() == url) { // get all deleted albums CoreDbAccess access; QList albumsToDelete; QList imagesToRemove; addAlbumChildrenToList(albumsToDelete, album); foreach (int albumId, albumsToDelete) { imagesToRemove << access.db()->getItemIDsInAlbum(albumId); } foreach (const qlonglong& imageId, imagesToRemove) { access.db()->removeAllImageRelationsFrom(imageId, DatabaseRelation::Grouped); } access.db()->removeItemsPermanently(imagesToRemove, albumsToDelete); } else { ItemInfo info = data->findItemInfo(url); if (!info.isNull()) { int originalVersionTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion()); int needTaggingTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::needTaggingHistoryGraph()); QList imageIds = CoreDbAccess().db()->getImagesRelatedFrom(info.id(), DatabaseRelation::DerivedFrom); CoreDbAccess access; foreach (const qlonglong& id, imageIds) { access.db()->removeItemTag(id, originalVersionTag); access.db()->addItemTag(id, needTaggingTag); } access.db()->removeAllImageRelationsFrom(info.id(), DatabaseRelation::Grouped); access.db()->removeItemsPermanently(QList() << info.id(), QList() << info.albumId()); } } } else if (operation == IOJobData::Trash) { ItemInfo info = data->findItemInfo(url); if (!info.isNull()) { int originalVersionTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion()); int needTaggingTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::needTaggingHistoryGraph()); QList imageIds = CoreDbAccess().db()->getImagesRelatedFrom(info.id(), DatabaseRelation::DerivedFrom); CoreDbAccess access; foreach (const qlonglong& id, imageIds) { access.db()->removeItemTag(id, originalVersionTag); access.db()->addItemTag(id, needTaggingTag); } access.db()->removeItems(QList() << info.id(), QList() << info.albumId()); } } else if (operation == IOJobData::Rename) { ItemInfo info = data->findItemInfo(url); if (!info.isNull()) { QString oldPath = url.toLocalFile(); QString newName = data->destUrl(url).fileName(); QString newPath = data->destUrl(url).toLocalFile(); if (data->overwrite()) { ThumbsDbAccess().db()->removeByFilePath(newPath); LoadingCacheInterface::fileChanged(newPath, false); CoreDbAccess().db()->deleteItem(info.albumId(), newName); } ThumbsDbAccess().db()->renameByFilePath(oldPath, newPath); // Remove old thumbnails and images from the cache LoadingCacheInterface::fileChanged(oldPath, false); // Rename in ItemInfo and database info.setName(newName); } } // Scan folders for changes if (operation == IOJobData::Delete || operation == IOJobData::Trash || operation == IOJobData::MoveAlbum) { PAlbum* const album = data->srcAlbum(); QString scanPath; if (album) { PAlbum* const parent = dynamic_cast(album->parent()); if (parent) { scanPath = parent->fileUrl().toLocalFile(); } } if (scanPath.isEmpty()) { scanPath = url.adjusted(QUrl::RemoveFilename).toLocalFile(); } ScanController::instance()->scheduleCollectionScanRelaxed(scanPath); } if (operation == IOJobData::CopyImage || operation == IOJobData::CopyAlbum || operation == IOJobData::CopyFiles || operation == IOJobData::MoveImage || operation == IOJobData::MoveAlbum || operation == IOJobData::MoveFiles) { QString scanPath = data->destUrl().toLocalFile(); ScanController::instance()->scheduleCollectionScanRelaxed(scanPath); } if (operation == IOJobData::Restore) { QString scanPath = url.adjusted(QUrl::RemoveFilename).toLocalFile(); ScanController::instance()->scheduleCollectionScanRelaxed(scanPath); } ProgressItem* const item = getProgressItem(data); if (item) { item->advance(1); } } QString DIO::getItemString(IOJobData* const data) const { switch (data->operation()) { case IOJobData::CopyAlbum: return i18n("Copy Album"); case IOJobData::CopyImage: return i18n("Copy Images"); case IOJobData::CopyFiles: return i18n("Copy Files"); case IOJobData::MoveAlbum: return i18n("Move Album"); case IOJobData::MoveImage: return i18n("Move Images"); case IOJobData::MoveFiles: return i18n("Move Files"); case IOJobData::Delete: return i18n("Delete"); case IOJobData::Trash: return i18n("Trash"); case IOJobData::Restore: return i18n("Restore Trash"); case IOJobData::Empty: return i18n("Empty Trash"); default: break; } return QString(); } ProgressItem* DIO::getProgressItem(IOJobData* const data) const { QString itemId = data->getProgressId(); if (itemId.isEmpty()) { return nullptr; } return ProgressManager::instance()->findItembyId(itemId); } void DIO::slotCancel(ProgressItem* item) { if (item) { item->setComplete(); } } void DIO::addAlbumChildrenToList(QList& list, Album* const album) { // simple recursive helper function if (album) { if (!list.contains(album->id())) { list.append(album->id()); } AlbumIterator it(album); while (it.current()) { addAlbumChildrenToList(list, *it); ++it; } } } } // namespace Digikam diff --git a/core/libs/database/utils/ifaces/diofinders.cpp b/core/libs/database/utils/ifaces/diofinders.cpp index 06b1d05aee..45a1ff4371 100644 --- a/core/libs/database/utils/ifaces/diofinders.cpp +++ b/core/libs/database/utils/ifaces/diofinders.cpp @@ -1,154 +1,137 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-17 * Description : low level files management interface - Finder classes * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2012-2013 by Marcel Wiesweg * Copyright (C) 2015 by Mohamed_Anwer * Copyright (C) 2018 by Maik Qualmann * * 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 "diofinders.h" // Qt includes #include #include // Local includes #include "digikam_debug.h" #include "iteminfo.h" #include "metaenginesettings.h" namespace Digikam { SidecarFinder::SidecarFinder(const QList& files) { // First, the sidecar urls will be added so that they are first copied or renamed. - const QString xmp(QLatin1String(".xmp")); + QStringList sidecarExtensions; + sidecarExtensions << QLatin1String("xmp"); + sidecarExtensions << MetaEngineSettings::instance()->settings().sidecarExtensions; foreach (const QUrl& url, files) { QFileInfo info(url.toLocalFile()); - if (!info.filePath().endsWith(xmp)) + foreach (const QString& ext, sidecarExtensions) { - QFileInfo extInfo(info.filePath() + xmp); - QFileInfo basInfo(info.path() + - QLatin1Char('/') + - info.completeBaseName() + xmp); - - if (extInfo.exists()) - { - localFiles << QUrl::fromLocalFile(extInfo.filePath()); - localFileModes << true; - localFileSuffixes << xmp; - qCDebug(DIGIKAM_DATABASE_LOG) << "Detected a sidecar" << localFiles.last(); - } + QString suffix(QLatin1Char('.') + ext); - if (basInfo.exists()) + if (info.filePath().endsWith(suffix)) { - localFiles << QUrl::fromLocalFile(basInfo.filePath()); - localFileModes << false; - localFileSuffixes << xmp; - qCDebug(DIGIKAM_DATABASE_LOG) << "Detected a sidecar" << localFiles.last(); + continue; } - } - - foreach (const QString& ext, MetaEngineSettings::instance()->settings().sidecarExtensions) - { - QString suffix(QLatin1Char('.') + ext); QFileInfo extInfo(info.filePath() + suffix); QFileInfo basInfo(info.path() + QLatin1Char('/') + info.completeBaseName() + suffix); if (extInfo.exists() && !localFiles.contains(QUrl::fromLocalFile(extInfo.filePath()))) { localFiles << QUrl::fromLocalFile(extInfo.filePath()); localFileModes << true; localFileSuffixes << suffix; qCDebug(DIGIKAM_DATABASE_LOG) << "Detected a sidecar" << localFiles.last(); } if (basInfo.exists() && !localFiles.contains(QUrl::fromLocalFile(basInfo.filePath()))) { localFiles << QUrl::fromLocalFile(basInfo.filePath()); localFileModes << false; localFileSuffixes << suffix; qCDebug(DIGIKAM_DATABASE_LOG) << "Detected a sidecar" << localFiles.last(); } } } // Now the files, if the user has selected sidecars, these are ignored. foreach (const QUrl& url, files) { if (!localFiles.contains(url)) { localFiles << url; localFileModes << true; localFileSuffixes << QString(); } } } // ------------------------------------------------------------------------------------------------ /** * TODO: Groups should not be resolved in dio, it should be handled in views. * This is already done for most things except for drag&drop, which is hard :) */ GroupedImagesFinder::GroupedImagesFinder(const QList& source) { QSet ids; foreach (const ItemInfo& info, source) { ids << info.id(); } infos.reserve(source.size()); foreach (const ItemInfo& info, source) { infos << info; if (info.hasGroupedImages()) { foreach (const ItemInfo& groupedImage, info.groupedImages()) { if (ids.contains(groupedImage.id())) { continue; } infos << groupedImage; ids << groupedImage.id(); } } } } } // namespace Digikam diff --git a/core/libs/database/utils/widgets/dbsettingswidget.cpp b/core/libs/database/utils/widgets/dbsettingswidget.cpp index 984a7f52f1..6d652d3927 100644 --- a/core/libs/database/utils/widgets/dbsettingswidget.cpp +++ b/core/libs/database/utils/widgets/dbsettingswidget.cpp @@ -1,855 +1,860 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-11-14 * Description : database settings widget * * Copyright (C) 2009-2010 by Holger Foerster * Copyright (C) 2010-2020 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 "dbsettingswidget_p.h" namespace Digikam { DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* const parent) : QWidget(parent), d(new Private) { setupMainArea(); } DatabaseSettingsWidget::~DatabaseSettingsWidget() { delete d; } void DatabaseSettingsWidget::setupMainArea() { QVBoxLayout* const layout = new QVBoxLayout(); setLayout(layout); // -------------------------------------------------------- const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QGroupBox* const dbConfigBox = new QGroupBox(i18n("Database Configuration"), this); QVBoxLayout* const vlay = new QVBoxLayout(dbConfigBox); DHBox* const typeHbox = new DHBox(); QLabel* const databaseTypeLabel = new QLabel(typeHbox); d->dbType = new QComboBox(typeHbox); databaseTypeLabel->setText(i18n("Type:")); // --------- fill with default values --------------------- int dbTypeIdx = 0; d->dbType->addItem(i18n("SQLite"), SQlite); d->dbTypeMap[SQlite] = dbTypeIdx++; #ifdef HAVE_MYSQLSUPPORT # ifdef HAVE_INTERNALMYSQL d->dbType->addItem(i18n("Mysql Internal (experimental)"), MysqlInternal); d->dbTypeMap[MysqlInternal] = dbTypeIdx++; # endif d->dbType->addItem(i18n("MySQL Server (experimental)"), MysqlServer); d->dbTypeMap[MysqlServer] = dbTypeIdx++; #endif QString tip = i18n("

Select here the type of database backend.

" "

SQlite backend is for local database storage with a small or medium collection sizes. " "It is the default and recommended backend for collections with less than 100K items.

"); #ifdef HAVE_MYSQLSUPPORT # ifdef HAVE_INTERNALMYSQL tip.append(i18n("

MySQL Internal backend is for local database storage with huge collection sizes. " "This backend is recommend for local collections with more than 100K items.

" "

Be careful: this one still in experimental stage.

")); # endif tip.append(i18n("

MySQL Server backend is a more robust solution especially for remote and shared database storage. " "It is also more efficient to manage huge collection sizes with more than 100K items.

" "

Be careful: this one still in experimental stage.

")); #endif d->dbType->setToolTip(tip); // -------------------------------------------------------- d->dbPathLabel = new QLabel(i18n("

Set here the location where the database files will be stored on your system. " "There are three databases: one for all collections properties, " "one to store compressed thumbnails, " "and one to store faces recognition metadata.
" "Write access is required to be able to edit image properties.

" "

Databases are digiKam core engines. Take care to use a place hosted by fast " "hardware (as SSD) with enough free space especially for thumbnails database.

" "

Note: a remote file system such as NFS, cannot be used here. " "For performance reasons, it is also recommended not to use removable media.

" "

"), dbConfigBox); d->dbPathLabel->setWordWrap(true); d->dbPathEdit = new DFileSelector(dbConfigBox); d->dbPathEdit->setFileDlgMode(QFileDialog::Directory); // -------------------------------------------------------- d->mysqlCmdBox = new DVBox(dbConfigBox); d->mysqlCmdBox->layout()->setContentsMargins(0, 0, 0, 0); new DLineWidget(Qt::Horizontal, d->mysqlCmdBox); QLabel* const mysqlBinariesLabel = new QLabel(i18n("

Here you can configure locations where MySQL binary tools are located. " "digiKam will try to find these binaries automatically if they are " "already installed on your computer.

"), d->mysqlCmdBox); mysqlBinariesLabel->setWordWrap(true); QGroupBox* const binaryBox = new QGroupBox(d->mysqlCmdBox); QGridLayout* const binaryLayout = new QGridLayout; binaryBox->setLayout(binaryLayout); binaryBox->setTitle(i18nc("@title:group", "MySQL Binaries")); d->dbBinariesWidget = new DBinarySearch(binaryBox); d->dbBinariesWidget->header()->setSectionHidden(2, true); d->dbBinariesWidget->addBinary(d->mysqlInitBin); d->dbBinariesWidget->addBinary(d->mysqlServBin); #ifdef Q_OS_LINUX d->dbBinariesWidget->addDirectory(QLatin1String("/usr/bin")); d->dbBinariesWidget->addDirectory(QLatin1String("/usr/sbin")); #endif #ifdef Q_OS_OSX // Std Macports install d->dbBinariesWidget->addDirectory(QLatin1String("/opt/local/bin")); d->dbBinariesWidget->addDirectory(QLatin1String("/opt/local/sbin")); d->dbBinariesWidget->addDirectory(QLatin1String("/opt/local/lib/mariadb/bin")); // digiKam Bundle PKG install d->dbBinariesWidget->addDirectory(QLatin1String("/opt/digikam/bin")); d->dbBinariesWidget->addDirectory(QLatin1String("/opt/digikam/sbin")); d->dbBinariesWidget->addDirectory(QLatin1String("/opt/digikam/lib/mariadb/bin")); #endif #ifdef Q_OS_WIN d->dbBinariesWidget->addDirectory(QLatin1String("C:/Program Files/MariaDB 10.3/bin")); d->dbBinariesWidget->addDirectory(QLatin1String("C:/Program Files (x86/MariaDB 10.3/bin")); #endif d->dbBinariesWidget->allBinariesFound(); // -------------------------------------------------------- d->tab = new QTabWidget(this); QLabel* const hostNameLabel = new QLabel(i18n("Host Name:")); d->hostName = new QLineEdit(); d->hostName->setPlaceholderText(i18n("Set the host computer name")); d->hostName->setToolTip(i18n("This is the computer name running MySQL server.\nThis can be \"localhost\" for a local server, " "or the network computer\n name (or IP address) in case of remote computer.")); QLabel* const connectOptsLabel = new QLabel(i18n("Connect options:")); connectOptsLabel->setOpenExternalLinks(true); d->connectOpts = new QLineEdit(); d->connectOpts->setPlaceholderText(i18n("Set the database connection options")); d->connectOpts->setToolTip(i18n("Set the MySQL server connection options.\nFor advanced users only.")); QLabel* const userNameLabel = new QLabel(i18n("User:")); d->userName = new QLineEdit(); d->userName->setPlaceholderText(i18n("Set the database account name")); d->userName->setToolTip(i18n("Set the MySQL server account name used\nby digiKam to be connected to the server.\n" "This account must be available on the remote MySQL server when database have been created.")); QLabel* const passwordLabel = new QLabel(i18n("Password:")); d->password = new QLineEdit(); d->password->setToolTip(i18n("Set the MySQL server account password used\nby digiKam to be connected to the server.\n" "You can left this field empty to use an account set without password.")); d->password->setEchoMode(QLineEdit::Password); DHBox* const phbox = new DHBox(); QLabel* const hostPortLabel = new QLabel(i18n("Host Port:")); d->hostPort = new QSpinBox(phbox); d->hostPort->setToolTip(i18n("Set the host computer port.\nUsually, MySQL server use port number 3306 by default")); d->hostPort->setMaximum(65535); d->hostPort->setValue(3306); QWidget* const space = new QWidget(phbox); phbox->setStretchFactor(space, 10); QPushButton* const checkDBConnectBtn = new QPushButton(i18n("Check Connection"), phbox); checkDBConnectBtn->setToolTip(i18n("Run a basic database connection to see if current MySQL server settings is suitable.")); // Only accept printable Ascii char for database names. QRegExp asciiRx(QLatin1String("[\x20-\x7F]+$")); QValidator* const asciiValidator = new QRegExpValidator(asciiRx, this); QLabel* const dbNameCoreLabel = new QLabel(i18n("Core Db Name:")); d->dbNameCore = new QLineEdit(); d->dbNameCore->setPlaceholderText(i18n("Set the core database name")); d->dbNameCore->setToolTip(i18n("The core database is lead digiKam container used to store\nalbums, items, and searches metadata.")); d->dbNameCore->setValidator(asciiValidator); QLabel* const dbNameThumbsLabel = new QLabel(i18n("Thumbs Db Name:")); d->dbNameThumbs = new QLineEdit(); d->dbNameThumbs->setPlaceholderText(i18n("Set the thumbnails database name")); d->dbNameThumbs->setToolTip(i18n("The thumbnails database is used by digiKam to host\nimage thumbs with wavelets compression images.\n" "This one can use quickly a lots of space,\nespecially if you have huge collections.")); d->dbNameThumbs->setValidator(asciiValidator); QLabel* const dbNameFaceLabel = new QLabel(i18n("Face Db Name:")); d->dbNameFace = new QLineEdit(); d->dbNameFace->setPlaceholderText(i18n("Set the face database name")); d->dbNameFace->setToolTip(i18n("The face database is used by digiKam to host image histograms\ndedicated to faces recognition process.\n" "This one can use quickly a lots of space, especially\nif you a lots of image with people faces detected " "and tagged.")); d->dbNameFace->setValidator(asciiValidator); QLabel* const dbNameSimilarityLabel = new QLabel(i18n("Similarity Db Name:")); d->dbNameSimilarity = new QLineEdit(); d->dbNameSimilarity->setPlaceholderText(i18n("Set the similarity database name")); d->dbNameSimilarity->setToolTip(i18n("The similarity database is used by digiKam to host image Haar matrix data for the similarity search.")); d->dbNameSimilarity->setValidator(asciiValidator); QPushButton* const defaultValuesBtn = new QPushButton(i18n("Default Settings")); defaultValuesBtn->setToolTip(i18n("Reset database names settings to common default values.")); d->expertSettings = new QGroupBox(); d->expertSettings->setFlat(true); QFormLayout* const expertSettinglayout = new QFormLayout(); d->expertSettings->setLayout(expertSettinglayout); expertSettinglayout->addRow(hostNameLabel, d->hostName); expertSettinglayout->addRow(userNameLabel, d->userName); expertSettinglayout->addRow(passwordLabel, d->password); expertSettinglayout->addRow(connectOptsLabel, d->connectOpts); expertSettinglayout->addRow(hostPortLabel, phbox); expertSettinglayout->addRow(new DLineWidget(Qt::Horizontal, d->expertSettings)); expertSettinglayout->addRow(dbNameCoreLabel, d->dbNameCore); expertSettinglayout->addRow(dbNameThumbsLabel, d->dbNameThumbs); expertSettinglayout->addRow(dbNameFaceLabel, d->dbNameFace); expertSettinglayout->addRow(dbNameSimilarityLabel, d->dbNameSimilarity); expertSettinglayout->addRow(new QWidget(), defaultValuesBtn); d->tab->addTab(d->expertSettings, i18n("Remote Server Settings")); // -------------------------------------------------------- d->dbNoticeBox = new QGroupBox(i18n("Database Server Instructions"), this); QVBoxLayout* const vlay2 = new QVBoxLayout(d->dbNoticeBox); QLabel* const notice = new QLabel(i18n("

digiKam expects that database is already created with a dedicated user account. " "This user name digikam will require full access to the database.
" "If your database is not already set up, you can use the following SQL commands " "(after replacing the password with the correct one).

"), d->dbNoticeBox); notice->setWordWrap(true); d->sqlInit = new QTextBrowser(d->dbNoticeBox); d->sqlInit->setOpenExternalLinks(false); d->sqlInit->setOpenLinks(false); d->sqlInit->setReadOnly(false); QLabel* const notice2 = new QLabel(i18n("

Note: with a Linux server, a database can be initialized following the commands below:

" "

# su

" "

# systemctl restart mysqld

" "

# mysql -u root

" "

...

" "

Enter SQL code to Mysql prompt in order to init digiKam databases with grant privileges (see behind)

" "

...

" "

quit

" "

NOTE: If you have an enormous collection, you should start MySQL server with " "mysql --max_allowed_packet=128M OR in my.ini or ~/.my.cnf, change the settings

"), d->dbNoticeBox); notice2->setWordWrap(true); notice2->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); vlay2->addWidget(notice); vlay2->addWidget(d->sqlInit); vlay2->addWidget(notice2); vlay2->setContentsMargins(spacing, spacing, spacing, spacing); vlay2->setSpacing(spacing); d->tab->addTab(d->dbNoticeBox, i18n("Requirements")); // -------------------------------------------------------- d->dbDetailsBox = new QGroupBox(i18n("Database Server Technical Details"), this); QVBoxLayout* const vlay3 = new QVBoxLayout(d->dbDetailsBox); QLabel* const details = new QLabel(i18n("

Use this configuration view to set all information " "to be connected to a remote " "Mysql database server " "(or MariaDB) " "through a network. " "As with Sqlite or MySQL internal server, 3 databases will be stored " "on the remote server: one for all collections properties, " "one to store compressed thumbnails, and one to store faces " "recognition metadata.

" "

Unlike Sqlite or MySQL internal server, you can customize the " "database names to simplify your backups.

" "

Databases are digiKam core engines. To prevent performance issues, " "take a care to use a fast network link between the client and the server " "computers. It is also recommended to host database files on " "fast hardware (as SSD) " "with enough free space, especially for thumbnails database, even if data are compressed using wavelets image format " "PGF.

" "

The databases must be created previously on the remote server by the administrator. " "Look in Requirements tab for details.

"), d->dbDetailsBox); details->setWordWrap(true); details->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); vlay3->addWidget(details); vlay3->setContentsMargins(spacing, spacing, spacing, spacing); vlay3->setSpacing(spacing); d->tab->addTab(d->dbDetailsBox, i18n("Documentation")); // -------------------------------------------------------- vlay->addWidget(typeHbox); vlay->addWidget(new DLineWidget(Qt::Horizontal)); vlay->addWidget(d->dbPathLabel); vlay->addWidget(d->dbPathEdit); vlay->addWidget(d->mysqlCmdBox); vlay->addWidget(d->tab); vlay->setContentsMargins(spacing, spacing, spacing, spacing); vlay->setSpacing(spacing); // -------------------------------------------------------- layout->setContentsMargins(QMargins()); layout->setSpacing(spacing); layout->addWidget(dbConfigBox); layout->addStretch(); // -------------------------------------------------------- connect(d->dbType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotHandleDBTypeIndexChanged(int))); connect(checkDBConnectBtn, SIGNAL(clicked()), this, SLOT(slotCheckMysqlServerConnection())); connect(defaultValuesBtn, SIGNAL(clicked()), this, SLOT(slotResetMysqlServerDBNames())); connect(d->dbNameCore, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSqlInit())); connect(d->dbNameThumbs, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSqlInit())); connect(d->dbNameFace, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSqlInit())); connect(d->dbNameSimilarity, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSqlInit())); connect(d->userName, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSqlInit())); slotHandleDBTypeIndexChanged(d->dbType->currentIndex()); } int DatabaseSettingsWidget::databaseType() const { return d->dbType->currentData().toInt(); } QString DatabaseSettingsWidget::databasePath() const { return d->dbPathEdit->fileDlgPath(); } void DatabaseSettingsWidget::setDatabasePath(const QString& path) { d->dbPathEdit->setFileDlgPath(path); } DbEngineParameters DatabaseSettingsWidget::orgDatabasePrm() const { return d->orgPrms; } QString DatabaseSettingsWidget::databaseBackend() const { switch (databaseType()) { case MysqlInternal: case MysqlServer: { return DbEngineParameters::MySQLDatabaseType(); } default: // SQlite { return DbEngineParameters::SQLiteDatabaseType(); } } } void DatabaseSettingsWidget::slotResetMysqlServerDBNames() { d->dbNameCore->setText(QLatin1String("digikam")); d->dbNameThumbs->setText(QLatin1String("digikam")); d->dbNameFace->setText(QLatin1String("digikam")); d->dbNameSimilarity->setText(QLatin1String("digikam")); } void DatabaseSettingsWidget::slotHandleDBTypeIndexChanged(int index) { int dbType = d->dbType->itemData(index).toInt(); setDatabaseInputFields(dbType); handleInternalServer(dbType); slotUpdateSqlInit(); } void DatabaseSettingsWidget::setDatabaseInputFields(int index) { switch (index) { case SQlite: { d->dbPathLabel->setVisible(true); d->dbPathEdit->setVisible(true); d->mysqlCmdBox->setVisible(false); d->tab->setVisible(false); connect(d->dbPathEdit->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotDatabasePathEditedDelayed())); break; } case MysqlInternal: { d->dbPathLabel->setVisible(true); d->dbPathEdit->setVisible(true); d->mysqlCmdBox->setVisible(true); d->tab->setVisible(false); connect(d->dbPathEdit->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotDatabasePathEditedDelayed())); break; } default: // MysqlServer { d->dbPathLabel->setVisible(false); d->dbPathEdit->setVisible(false); d->mysqlCmdBox->setVisible(false); d->tab->setVisible(true); disconnect(d->dbPathEdit->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotDatabasePathEditedDelayed())); break; } } } void DatabaseSettingsWidget::handleInternalServer(int index) { bool internal = (index == MysqlInternal); d->hostName->setDisabled(internal); d->hostPort->setDisabled(internal); d->dbNameCore->setDisabled(internal); d->dbNameThumbs->setDisabled(internal); d->dbNameFace->setDisabled(internal); d->dbNameSimilarity->setDisabled(internal); d->userName->setDisabled(internal); d->password->setDisabled(internal); d->connectOpts->setDisabled(internal); } void DatabaseSettingsWidget::slotUpdateSqlInit() { QString sql = QString::fromLatin1("CREATE USER \'%1\'@\'%2\' IDENTIFIED BY \'password\';
") .arg(d->userName->text()) .arg(d->hostName->text()); sql += QString::fromLatin1("GRANT ALL ON *.* TO \'%1\'@\'%2\' IDENTIFIED BY \'password\';
") .arg(d->userName->text()) .arg(d->hostName->text()); sql += QString::fromLatin1("CREATE DATABASE %1;
" "GRANT ALL PRIVILEGES ON %2.* TO \'%3\'@\'%4\';
") .arg(d->dbNameCore->text()) .arg(d->dbNameCore->text()) .arg(d->userName->text()) .arg(d->hostName->text()); if (d->dbNameThumbs->text() != d->dbNameCore->text()) { sql += QString::fromLatin1("CREATE DATABASE %1;
" "GRANT ALL PRIVILEGES ON %2.* TO \'%3\'@\'%4\';
") .arg(d->dbNameThumbs->text()) .arg(d->dbNameThumbs->text()) .arg(d->userName->text()) .arg(d->hostName->text()); } if ((d->dbNameFace->text() != d->dbNameCore->text()) && (d->dbNameFace->text() != d->dbNameThumbs->text())) { sql += QString::fromLatin1("CREATE DATABASE %1;
" "GRANT ALL PRIVILEGES ON %2.* TO \'%3\'@\'%4\';
") .arg(d->dbNameFace->text()) .arg(d->dbNameFace->text()) .arg(d->userName->text()) .arg(d->hostName->text()); } if ((d->dbNameSimilarity->text() != d->dbNameCore->text()) && (d->dbNameSimilarity->text() != d->dbNameThumbs->text()) && (d->dbNameSimilarity->text() != d->dbNameFace->text())) { sql += QString::fromLatin1("CREATE DATABASE %1;
" "GRANT ALL PRIVILEGES ON %2.* TO \'%3\'@\'%4\';
") .arg(d->dbNameSimilarity->text()) .arg(d->dbNameSimilarity->text()) .arg(d->userName->text()) .arg(d->hostName->text()); } sql += QLatin1String("FLUSH PRIVILEGES;
"); d->sqlInit->setText(sql); } void DatabaseSettingsWidget::slotCheckMysqlServerConnection() { QString error; if (checkMysqlServerConnection(error)) { QMessageBox::information(qApp->activeWindow(), i18n("Database connection test"), i18n("Database connection test successful.")); } else { QMessageBox::critical(qApp->activeWindow(), i18n("Database connection test"), i18n("Database connection test was not successful.

Error was: %1

", error)); } } bool DatabaseSettingsWidget::checkMysqlServerConnectionConfig(QString& error) { if (d->hostName->text().isEmpty()) { error = i18n("The server hostname is empty"); return false; } if (d->userName->text().isEmpty()) { error = i18n("The server user name is empty"); return false; } return true; } bool DatabaseSettingsWidget::checkMysqlServerDbNamesConfig(QString& error) { if (d->dbNameCore->text().isEmpty()) { error = i18n("The core database name is empty"); return false; } if (d->dbNameThumbs->text().isEmpty()) { error = i18n("The thumbnails database name is empty"); return false; } if (d->dbNameFace->text().isEmpty()) { error = i18n("The face database name is empty"); return false; } if (d->dbNameSimilarity->text().isEmpty()) { error = i18n("The similarity database name is empty"); return false; } return true; } bool DatabaseSettingsWidget::checkMysqlServerConnection(QString& error) { if (!checkMysqlServerConnectionConfig(error)) { return false; } bool result = false; qApp->setOverrideCursor(Qt::WaitCursor); AlbumManager::instance()->addFakeConnection(); QString databaseID(QLatin1String("ConnectionTest")); { QSqlDatabase testDatabase = QSqlDatabase::addDatabase(databaseBackend(), databaseID); DbEngineParameters prm = getDbEngineParameters(); qCDebug(DIGIKAM_DATABASE_LOG) << "Testing DB connection (" << databaseID << ") with these settings:"; qCDebug(DIGIKAM_DATABASE_LOG) << prm; testDatabase.setHostName(prm.hostName); testDatabase.setPort(prm.port); testDatabase.setUserName(prm.userName); testDatabase.setPassword(prm.password); testDatabase.setConnectOptions(prm.connectOptions); result = testDatabase.open(); error = testDatabase.lastError().text(); testDatabase.close(); } QSqlDatabase::removeDatabase(databaseID); qApp->restoreOverrideCursor(); return result; } void DatabaseSettingsWidget::setParametersFromSettings(const ApplicationSettings* const settings, const bool& migration) { d->orgPrms = settings->getDbEngineParameters(); if (d->orgPrms.databaseType == DbEngineParameters::SQLiteDatabaseType()) { d->dbPathEdit->setFileDlgPath(d->orgPrms.getCoreDatabaseNameOrDir()); d->dbType->setCurrentIndex(d->dbTypeMap[SQlite]); slotResetMysqlServerDBNames(); if (settings->getDatabaseDirSetAtCmd() && !migration) { d->dbType->setEnabled(false); d->dbPathEdit->setEnabled(false); d->dbPathLabel->setText(d->dbPathLabel->text() + i18n("This path was set as a command line" "option (--database-directory).")); } } #ifdef HAVE_MYSQLSUPPORT # ifdef HAVE_INTERNALMYSQL else if (d->orgPrms.databaseType == DbEngineParameters::MySQLDatabaseType() && d->orgPrms.internalServer) { d->dbPathEdit->setFileDlgPath(d->orgPrms.internalServerPath()); d->dbType->setCurrentIndex(d->dbTypeMap[MysqlInternal]); d->mysqlInitBin.setup(QFileInfo(d->orgPrms.internalServerMysqlInitCmd).absoluteFilePath()); d->mysqlServBin.setup(QFileInfo(d->orgPrms.internalServerMysqlServCmd).absoluteFilePath()); d->dbBinariesWidget->allBinariesFound(); slotResetMysqlServerDBNames(); } # endif else { d->dbType->setCurrentIndex(d->dbTypeMap[MysqlServer]); d->dbNameCore->setText(d->orgPrms.databaseNameCore); d->dbNameThumbs->setText(d->orgPrms.databaseNameThumbnails); d->dbNameFace->setText(d->orgPrms.databaseNameFace); d->dbNameSimilarity->setText(d->orgPrms.databaseNameSimilarity); d->hostName->setText(d->orgPrms.hostName); d->hostPort->setValue((d->orgPrms.port == -1) ? 3306 : d->orgPrms.port); d->connectOpts->setText(d->orgPrms.connectOptions); d->userName->setText(d->orgPrms.userName); d->password->setText(d->orgPrms.password); } #endif slotHandleDBTypeIndexChanged(d->dbType->currentIndex()); } DbEngineParameters DatabaseSettingsWidget::getDbEngineParameters() const { DbEngineParameters prm; switch(databaseType()) { case SQlite: prm = DbEngineParameters::parametersForSQLiteDefaultFile(databasePath()); break; case MysqlInternal: prm = DbEngineParameters::defaultParameters(databaseBackend()); prm.setInternalServerPath(databasePath()); prm.internalServerMysqlInitCmd = d->mysqlInitBin.path(); prm.internalServerMysqlServCmd = d->mysqlServBin.path(); break; default: // MysqlServer prm.internalServer = false; prm.databaseType = databaseBackend(); prm.databaseNameCore = d->dbNameCore->text(); prm.databaseNameThumbnails = d->dbNameThumbs->text(); prm.databaseNameFace = d->dbNameFace->text(); prm.databaseNameSimilarity = d->dbNameSimilarity->text(); prm.connectOptions = d->connectOpts->text(); prm.hostName = d->hostName->text(); prm.port = d->hostPort->value(); prm.userName = d->userName->text(); prm.password = d->password->text(); break; } return prm; } void DatabaseSettingsWidget::slotDatabasePathEditedDelayed() { QTimer::singleShot(300, this, SLOT(slotDatabasePathEdited())); } void DatabaseSettingsWidget::slotDatabasePathEdited() { QString newPath = databasePath(); #ifndef Q_OS_WIN if (!newPath.isEmpty() && !QDir::isAbsolutePath(newPath)) { d->dbPathEdit->setFileDlgPath(QDir::homePath() + QLatin1Char('/') + newPath); } #endif d->dbPathEdit->setFileDlgPath(newPath); } bool DatabaseSettingsWidget::checkDatabaseSettings() { switch (databaseType()) { case SQlite: { return checkDatabasePath(); } case MysqlInternal: { if (!checkDatabasePath()) return false; if (!d->dbBinariesWidget->allBinariesFound()) return false; return true; } default: // MysqlServer { QString error; if (!checkMysqlServerDbNamesConfig(error)) { QMessageBox::critical(qApp->activeWindow(), i18n("Database Configuration"), i18n("The database names configuration is not valid. Error is

%1


" "Please check your configuration.", error)); return false; } if (!checkMysqlServerConnection(error)) { QMessageBox::critical(qApp->activeWindow(), i18n("Database Connection Test"), i18n("Testing database connection has failed with error

%1


" "Please check your configuration.", error)); return false; } } } return true; } bool DatabaseSettingsWidget::checkDatabasePath() { QString dbFolder = databasePath(); qCDebug(DIGIKAM_DATABASE_LOG) << "Database directory is : " << dbFolder; if (dbFolder.isEmpty()) { QMessageBox::information(qApp->activeWindow(), qApp->applicationName(), i18n("You must select a folder for digiKam to " "store information and metadata in a database file.")); return false; } QDir targetPath(dbFolder); if (!targetPath.exists()) { int rc = QMessageBox::question(qApp->activeWindow(), i18n("Create Database Folder?"), i18n("

The folder to put your database in does not seem to exist:

" "

%1

" "Would you like digiKam to create it for you?", dbFolder)); if (rc == QMessageBox::No) { return false; } if (!targetPath.mkpath(dbFolder)) { QMessageBox::information(qApp->activeWindow(), i18n("Create Database Folder Failed"), i18n("

digiKam could not create the folder to host your database file.\n" "Please select a different location.

" "

%1

", dbFolder)); return false; } } QFileInfo path(dbFolder); #ifdef Q_OS_WIN + // Work around bug #189168 + QTemporaryFile temp; - temp.setFileTemplate(dbFolder + QLatin1String("XXXXXX")); + temp.setFileTemplate(path.filePath() + QLatin1String("/XXXXXX")); if (!temp.open()) + #else + if (!path.isWritable()) + #endif { QMessageBox::information(qApp->activeWindow(), i18n("No Database Write Access"), i18n("

You do not seem to have write access " "for the folder to host the database file.
" "Please select a different location.

" "

%1

", dbFolder)); return false; } return true; } } // namespace Digikam diff --git a/core/libs/fileactionmanager/databaseworkeriface.cpp b/core/libs/fileactionmanager/databaseworkeriface.cpp index 2b20218a31..4850f3e3a3 100644 --- a/core/libs/fileactionmanager/databaseworkeriface.cpp +++ b/core/libs/fileactionmanager/databaseworkeriface.cpp @@ -1,388 +1,389 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-01-18 * Description : database worker interface * * Copyright (C) 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. * * ============================================================ */ #include "databaseworkeriface.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "collectionscanner.h" #include "coredboperationgroup.h" #include "iteminfotasksplitter.h" #include "fileactionmngr_p.h" #include "scancontroller.h" #include "disjointmetadata.h" +#include "faceutils.h" namespace Digikam { void FileActionMngrDatabaseWorker::assignTags(FileActionItemInfoList infos, const QList& tagIDs) { changeTags(infos, tagIDs, true); } void FileActionMngrDatabaseWorker::removeTags(FileActionItemInfoList infos, const QList& tagIDs) { changeTags(infos, tagIDs, false); } void FileActionMngrDatabaseWorker::changeTags(FileActionItemInfoList infos, const QList& tagIDs, bool addOrRemove) { DisjointMetadata hub; QList forWriting; { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } hub.load(info); for (QList::const_iterator tagIt = tagIDs.constBegin() ; tagIt != tagIDs.constEnd() ; ++tagIt) { if (addOrRemove) { hub.setTag(*tagIt, DisjointMetadata::MetadataAvailable); } else { hub.setTag(*tagIt, DisjointMetadata::MetadataInvalid); } } hub.write(info, DisjointMetadata::PartialWrite); if (hub.willWriteMetadata(DisjointMetadata::FullWriteIfChanged) && d->shallSendForWriting(info.id())) { forWriting << info; } infos.dbProcessedOne(); group.allowLift(); } } // send for writing file metadata if (!forWriting.isEmpty()) { FileActionItemInfoList forWritingTaskList = FileActionItemInfoList::continueTask(forWriting, infos.progress()); forWritingTaskList.schedulingForWrite(i18n("Writing metadata to files"), d->fileProgressCreator()); qCDebug(DIGIKAM_GENERAL_LOG) << "Scheduled to write"; for (ItemInfoTaskSplitter splitter(forWritingTaskList) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), MetadataHub::WRITE_TAGS); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::assignPickLabel(FileActionItemInfoList infos, int pickId) { DisjointMetadata hub; QList forWriting; { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } hub.load(info); hub.setPickLabel(pickId); hub.write(info, DisjointMetadata::PartialWrite); if (hub.willWriteMetadata(DisjointMetadata::FullWriteIfChanged) && d->shallSendForWriting(info.id())) { forWriting << info; } infos.dbProcessedOne(); group.allowLift(); } } // send for writing file metadata if (!forWriting.isEmpty()) { FileActionItemInfoList forWritingTaskList = FileActionItemInfoList::continueTask(forWriting, infos.progress()); forWritingTaskList.schedulingForWrite(i18n("Writing metadata to files"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(forWritingTaskList) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), MetadataHub::WRITE_PICKLABEL); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::assignColorLabel(FileActionItemInfoList infos, int colorId) { DisjointMetadata hub; QList forWriting; { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } hub.load(info); hub.setColorLabel(colorId); hub.write(info, DisjointMetadata::PartialWrite); if (hub.willWriteMetadata(DisjointMetadata::FullWriteIfChanged) && d->shallSendForWriting(info.id())) { forWriting << info; } infos.dbProcessedOne(); group.allowLift(); } } // send for writing file metadata if (!forWriting.isEmpty()) { FileActionItemInfoList forWritingTaskList = FileActionItemInfoList::continueTask(forWriting, infos.progress()); forWritingTaskList.schedulingForWrite(i18n("Writing metadata to files"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(forWritingTaskList) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), MetadataHub::WRITE_COLORLABEL); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::assignRating(FileActionItemInfoList infos, int rating) { DisjointMetadata hub; QList forWriting; rating = qMin(RatingMax, qMax(RatingMin, rating)); { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } hub.load(info); hub.setRating(rating); hub.write(info, DisjointMetadata::PartialWrite); if (hub.willWriteMetadata(DisjointMetadata::FullWriteIfChanged) && d->shallSendForWriting(info.id())) { forWriting << info; } infos.dbProcessedOne(); group.allowLift(); } } // send for writing file metadata if (!forWriting.isEmpty()) { FileActionItemInfoList forWritingTaskList = FileActionItemInfoList::continueTask(forWriting, infos.progress()); forWritingTaskList.schedulingForWrite(i18n("Writing metadata to files"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(forWritingTaskList) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), MetadataHub::WRITE_RATING); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::editGroup(int groupAction, const ItemInfo& pick, FileActionItemInfoList infos) { { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& constInfo, infos) { if (state() == WorkerObject::Deactivating) { break; } ItemInfo info(constInfo); switch (groupAction) { case AddToGroup: info.addToGroup(pick); break; case RemoveFromGroup: info.removeFromGroup(); break; case Ungroup: info.clearGroup(); break; } infos.dbProcessedOne(); group.allowLift(); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::setExifOrientation(FileActionItemInfoList infos, int orientation) { { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (ItemInfo info, infos) { if (state() == WorkerObject::Deactivating) { break; } - MetadataHub hub; - hub.adjustFaceRectangles(info, false, - orientation, - info.orientation()); + // Adjust Faces + + FaceUtils().rotateFaces(info, orientation, + info.orientation()); info.setOrientation(orientation); } } infos.dbProcessed(infos.count()); infos.schedulingForWrite(infos.count(), i18n("Revising Exif Orientation tags"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(infos) ; splitter.hasNext() ; ) { emit writeOrientationToFiles(FileActionItemInfoList(splitter.next()), orientation); } infos.dbFinished(); } void FileActionMngrDatabaseWorker::applyMetadata(FileActionItemInfoList infos, DisjointMetadata *hub) { { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } // apply to database hub->write(info); infos.dbProcessedOne(); group.allowLift(); } } if (hub->willWriteMetadata(DisjointMetadata::FullWriteIfChanged), Qt::DirectConnection) { int flags = hub->changedFlags(); // don't filter by shallSendForWriting here; we write from the hub, not from freshly loaded data infos.schedulingForWrite(infos.size(), i18n("Writing metadata to files"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(infos) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), flags); } } delete hub; infos.dbFinished(); } void FileActionMngrDatabaseWorker::copyAttributes(FileActionItemInfoList infos, const QStringList& derivedPaths) { if (infos.size() == 1) { foreach (const QString& path, derivedPaths) { if (state() == WorkerObject::Deactivating) { break; } ItemInfo dest = ScanController::instance()->scannedInfo(path); CollectionScanner::copyFileProperties(infos.first(), dest); } infos.dbProcessedOne(); } infos.dbFinished(); } } // namespace Digikam diff --git a/core/libs/fileactionmanager/fileworkeriface.cpp b/core/libs/fileactionmanager/fileworkeriface.cpp index 09ab9d9a6e..ec50c50716 100644 --- a/core/libs/fileactionmanager/fileworkeriface.cpp +++ b/core/libs/fileactionmanager/fileworkeriface.cpp @@ -1,331 +1,345 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-01-18 * Description : database worker interface * * Copyright (C) 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. * * ============================================================ */ #include "fileworkeriface.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "digikam_globals.h" #include "fileactionmngr_p.h" #include "metaenginesettings.h" #include "itemattributeswatch.h" #include "iteminfotasksplitter.h" #include "collectionscanner.h" +#include "filereadwritelock.h" #include "scancontroller.h" +#include "faceutils.h" #include "jpegutils.h" #include "dimg.h" namespace Digikam { void FileActionMngrFileWorker::writeOrientationToFiles(FileActionItemInfoList infos, int orientation) { QStringList failedItems; foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } QString path = info.filePath(); DMetadata metadata(path); DMetadata::ImageOrientation o = (DMetadata::ImageOrientation)orientation; metadata.setItemOrientation(o); if (!metadata.applyChanges()) { failedItems.append(info.name()); } else { emit imageDataChanged(path, true, true); QUrl url = QUrl::fromLocalFile(path); ItemAttributesWatch::instance()->fileMetadataChanged(url); } infos.writtenToOne(); } if (!failedItems.isEmpty()) { emit imageChangeFailed(i18n("Failed to revise Exif orientation these files:"), failedItems); } infos.finishedWriting(); } void FileActionMngrFileWorker::writeMetadataToFiles(FileActionItemInfoList infos) { d->startingToWrite(infos); ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { MetadataHub hub; if (state() == WorkerObject::Deactivating) { break; } hub.load(info); QString path = info.filePath(); if (MetaEngineSettings::instance()->settings().useLazySync) { hub.write(path, MetadataHub::WRITE_ALL); } else { ScanController::FileMetadataWrite writeScope(info); writeScope.changed(hub.write(path, MetadataHub::WRITE_ALL)); } // hub emits fileMetadataChanged infos.writtenToOne(); } ScanController::instance()->resumeCollectionScan(); infos.finishedWriting(); } void FileActionMngrFileWorker::writeMetadata(FileActionItemInfoList infos, int flags) { d->startingToWrite(infos); ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { MetadataHub hub; if (state() == WorkerObject::Deactivating) { break; } hub.load(info); // apply to file metadata if (MetaEngineSettings::instance()->settings().useLazySync) { hub.writeToMetadata(info, (MetadataHub::WriteComponents)flags); } else { ScanController::FileMetadataWrite writeScope(info); writeScope.changed(hub.writeToMetadata(info, (MetadataHub::WriteComponents)flags)); } // hub emits fileMetadataChanged infos.writtenToOne(); } ScanController::instance()->resumeCollectionScan(); infos.finishedWriting(); } void FileActionMngrFileWorker::transform(FileActionItemInfoList infos, int action) { d->startingToWrite(infos); QStringList failedItems; ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } + FileWriteLocker lock(info.filePath()); + QString path = info.filePath(); QString format = info.format(); MetaEngine::ImageOrientation currentOrientation = (MetaEngine::ImageOrientation)info.orientation(); bool isRaw = info.format().startsWith(QLatin1String("RAW")); bool rotateAsJpeg = false; bool rotateLossy = false; MetaEngineSettingsContainer::RotationBehaviorFlags behavior; behavior = MetaEngineSettings::instance()->settings().rotationBehavior; bool rotateByMetadata = (behavior & MetaEngineSettingsContainer::RotateByMetadataFlag); // Check if rotation by content, as desired, is feasible // We'll later check again if it was successful if (behavior & MetaEngineSettingsContainer::RotatingPixels) { if (format == QLatin1String("JPG") && JPEGUtils::isJpegImage(path)) { rotateAsJpeg = true; } if (behavior & MetaEngineSettingsContainer::RotateByLossyRotation) { DImg::FORMAT frmt = DImg::fileFormat(path); switch (frmt) { case DImg::JPEG: case DImg::PNG: case DImg::TIFF: case DImg::JP2K: case DImg::PGF: case DImg::HEIF: rotateLossy = true; default: break; } } } MetaEngineRotation matrix; matrix *= currentOrientation; matrix *= (MetaEngineRotation::TransformationAction)action; MetaEngine::ImageOrientation finalOrientation = matrix.exifOrientation(); bool rotatedPixels = false; if (rotateAsJpeg) { JPEGUtils::JpegRotator rotator(path); rotator.setCurrentOrientation(currentOrientation); if (action == MetaEngineRotation::NoTransformation) { rotatedPixels = rotator.autoExifTransform(); } else { rotatedPixels = rotator.exifTransform((MetaEngineRotation::TransformationAction)action); } if (!rotatedPixels) { failedItems.append(info.name()); } } else if (rotateLossy) { // Non-JPEG image: DImg DImg image; if (!image.load(path)) { failedItems.append(info.name()); } else { if (action == MetaEngineRotation::NoTransformation) { image.rotateAndFlip(currentOrientation); } else { image.transform(action); } // TODO: Atomic operation!! // prepare metadata, including to reset Exif tag image.prepareMetadataToSave(path, image.format(), true); if (image.save(path, image.detectedFormat())) { rotatedPixels = true; } else { failedItems.append(info.name()); } } } int newOrientation = finalOrientation; if (rotatedPixels) { finalOrientation = MetaEngine::ORIENTATION_NORMAL; } // DB rotation ItemInfo(info).setOrientation(finalOrientation); // Adjust Faces - MetadataHub hub; - hub.adjustFaceRectangles(info, rotatedPixels, - newOrientation, - currentOrientation); + QSize newSize = FaceUtils().rotateFaces(info, newOrientation, + currentOrientation); + if (newSize.isValid()) + { + MetadataHub hub; + hub.load(info); + + // Adjusted newSize + + newSize = rotatedPixels ? newSize : info.dimensions(); + + hub.loadFaceTags(info, newSize); + hub.write(info.filePath(), MetadataHub::WRITE_TAGS, true); + } if (rotateByMetadata) { // Setting the rotation flag on Raws with embedded JPEG is a mess // Can apply to the RAW data, or to the embedded JPEG, or to both. if (!isRaw) { DMetadata metadata(path); metadata.setItemOrientation(finalOrientation); metadata.applyChanges(); } } CollectionScanner scanner; scanner.scanFile(info, CollectionScanner::NormalScan); if (!failedItems.contains(info.name())) { emit imageDataChanged(path, true, true); ItemAttributesWatch::instance()->fileMetadataChanged(info.fileUrl()); } infos.writtenToOne(); } if (!failedItems.isEmpty()) { emit imageChangeFailed(i18n("Failed to transform these files:"), failedItems); } infos.finishedWriting(); ScanController::instance()->resumeCollectionScan(); } } // namespace Digikam diff --git a/core/libs/fileactionmanager/metadatahub.cpp b/core/libs/fileactionmanager/metadatahub.cpp index 45eee45db1..181dfc0aaf 100644 --- a/core/libs/fileactionmanager/metadatahub.cpp +++ b/core/libs/fileactionmanager/metadatahub.cpp @@ -1,913 +1,828 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-01-05 * Description : Metadata handling * * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2007-2020 by Gilles Caulier * Copyright (C) 2014-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 "metadatahub.h" // Qt includes #include #include #include // Local includes #include "digikam_debug.h" #include "coredbaccess.h" #include "coredbwatch.h" #include "iteminfo.h" #include "itemcomments.h" #include "itemposition.h" #include "template.h" #include "templatemanager.h" #include "applicationsettings.h" #include "itemattributeswatch.h" #include "tagscache.h" #include "facetagseditor.h" #include "metadatahubmngr.h" #ifdef HAVE_KFILEMETADATA # include "baloowrap.h" #endif namespace Digikam { class Q_DECL_HIDDEN MetadataHub::Private { public: explicit Private() : pickLabel(-1), colorLabel(-1), rating(-1), count(0), dateTimeStatus(MetadataHub::MetadataInvalid), titlesStatus(MetadataHub::MetadataInvalid), commentsStatus(MetadataHub::MetadataInvalid), pickLabelStatus(MetadataHub::MetadataInvalid), colorLabelStatus(MetadataHub::MetadataInvalid), ratingStatus(MetadataHub::MetadataInvalid), templateStatus(MetadataHub::MetadataInvalid) { } public: int pickLabel; int colorLabel; int rating; int count; QDateTime dateTime; CaptionsMap titles; CaptionsMap comments; Template metadataTemplate; QMap tags; QStringList tagList; QMultiMap faceTagsList; ItemPosition itemPosition; MetadataHub::Status dateTimeStatus; MetadataHub::Status titlesStatus; MetadataHub::Status commentsStatus; MetadataHub::Status pickLabelStatus; MetadataHub::Status colorLabelStatus; MetadataHub::Status ratingStatus; MetadataHub::Status templateStatus; public: template void loadSingleValue(const T& data, T& storage, MetadataHub::Status& status); }; // ------------------------------------------------------------------------------------------ MetadataHub::MetadataHub() : d(new Private) { } MetadataHub::MetadataHub(const MetadataHub& other) : d(new Private(*other.d)) { } MetadataHub::~MetadataHub() { delete d; } MetadataHub& MetadataHub::operator=(const MetadataHub& other) { (*d) = (*other.d); return *this; } MetadataHub* MetadataHub::clone() const { return (new MetadataHub(*this)); } void MetadataHub::reset() { (*d) = Private(); } // -------------------------------------------------- void MetadataHub::load(const ItemInfo& info) { d->count++; //qCDebug(DIGIKAM_GENERAL_LOG) << "---------------------------------Load from ItemInfo ----------------"; CaptionsMap commentMap; CaptionsMap titleMap; { CoreDbAccess access; ItemComments comments = info.imageComments(access); commentMap = comments.toCaptionsMap(); titleMap = comments.toCaptionsMap(DatabaseComment::Title); } Template tref = info.metadataTemplate(); Template t = TemplateManager::defaultManager()->findByContents(tref); //qCDebug(DIGIKAM_GENERAL_LOG) << "Found Metadata Template: " << t.templateTitle(); load(info.dateTime(), titleMap, commentMap, info.colorLabel(), info.pickLabel(), info.rating(), t.isNull() ? tref : t); QList tagIds = info.tagIds(); loadTags(tagIds); loadFaceTags(info, info.dimensions()); d->itemPosition = info.imagePosition(); } // private common code to merge tags void MetadataHub::loadTags(const QList& loadedTags) { d->tags.clear(); foreach (int tagId, loadedTags) { if (TagsCache::instance()->isInternalTag(tagId)) { continue; } d->tags[tagId] = MetadataAvailable; } } // private common code to load dateTime, comment, color label, pick label, rating void MetadataHub::load(const QDateTime& dateTime, const CaptionsMap& titles, const CaptionsMap& comments, int colorLabel, int pickLabel, int rating, const Template& t) { if (dateTime.isValid()) { d->loadSingleValue(dateTime, d->dateTime, d->dateTimeStatus); } d->loadSingleValue(pickLabel, d->pickLabel, d->pickLabelStatus); d->loadSingleValue(colorLabel, d->colorLabel, d->colorLabelStatus); d->loadSingleValue(rating, d->rating, d->ratingStatus); d->loadSingleValue(titles, d->titles, d->titlesStatus); d->loadSingleValue(comments, d->comments, d->commentsStatus); d->loadSingleValue