diff --git a/NEWS b/NEWS index a80a5531f9..08312ade29 100644 --- a/NEWS +++ b/NEWS @@ -1,546 +1,547 @@ digiKam 7.0.0-beta2 - Release date: 2020-01-26 ***************************************************************************************************** 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 simlified and embeded into left side-bar tab. SlideShow : Add new shuffle mode. HTMLGallery : Add new theme "Html5Responsive". General : Update internal libraw engine to last 201910 snapshot aka next 0.20.0 release (https://www.libraw.org/news/libraw-snapshot-201910) ***************************************************************************************************** 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 ==> +531 ==> 415944 - 7.0.0beta2 removed all my Geolocation bookmarks. +532 ==> diff --git a/core/dplugins/generic/metadata/geolocationedit/dialog/geolocationedit.cpp b/core/dplugins/generic/metadata/geolocationedit/dialog/geolocationedit.cpp index 74df2a0404..4043fc3d34 100644 --- a/core/dplugins/generic/metadata/geolocationedit/dialog/geolocationedit.cpp +++ b/core/dplugins/generic/metadata/geolocationedit/dialog/geolocationedit.cpp @@ -1,1091 +1,1092 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-05-16 * Description : A tool to edit geolocation * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2010-2014 by Michael G. Hansen * Copyright (C) 2010 by Gabriel Voicu * Copyright (C) 2014 by Justus Schwartz * * 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 "geolocationedit.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "dlayoutbox.h" #include "digikam_config.h" #include "itemmarkertiler.h" #include "trackmanager.h" #include "gpscommon.h" #include "gpsitemmodel.h" #include "mapdragdrophandler.h" #include "gpsitemlist.h" #include "gpsitemlistdragdrophandler.h" #include "gpsitemlistcontextmenu.h" #include "gpscorrelatorwidget.h" #include "digikam_debug.h" #include "dmessagebox.h" #include "rgwidget.h" #include "kmlwidget.h" #include "statusprogressbar.h" #include "searchwidget.h" #include "backend-rg.h" #include "gpsitemdetails.h" #include "gpsgeoifacemodelhelper.h" #include "dxmlguiwindow.h" #include "gpsbookmarkowner.h" #include "gpsbookmarkmodelhelper.h" #ifdef GPSSYNC_MODELTEST # include #endif using namespace Digikam; namespace DigikamGenericGeolocationEditPlugin { struct SaveChangedImagesHelper { public: explicit SaveChangedImagesHelper(GPSItemModel* const model) : imageModel(model) { } QPair operator()(const QPersistentModelIndex& itemIndex) { GPSItemContainer* const item = imageModel->itemFromIndex(itemIndex); if (!item) return QPair(QUrl(), QString()); return QPair(item->url(), item->saveChanges()); } public: typedef QPair result_type; GPSItemModel* const imageModel; }; // --------------------------------------------------------------------------------- struct LoadFileMetadataHelper { public: explicit LoadFileMetadataHelper(GPSItemModel* const model) : imageModel(model) { } QPair operator()(const QPersistentModelIndex& itemIndex) { GPSItemContainer* const item = imageModel->itemFromIndex(itemIndex); if (!item) return QPair(QUrl(), QString()); item->loadImageData(); return QPair(item->url(), QString()); } public: typedef QPair result_type; GPSItemModel* const imageModel; }; // --------------------------------------------------------------------------------- class Q_DECL_HIDDEN GeolocationEdit::Private { public: explicit Private() { imageModel = nullptr; selectionModel = nullptr; uiEnabled = true; listViewContextMenu = nullptr; trackManager = nullptr; fileIOFutureWatcher = nullptr; fileIOCountDone = 0; fileIOCountTotal = 0; fileIOCloseAfterSaving = false; buttonBox = nullptr; VSplitter = nullptr; HSplitter = nullptr; treeView = nullptr; stackedWidget = nullptr; tabBar = nullptr; splitterSize = 0; undoStack = nullptr; undoView = nullptr; progressBar = nullptr; progressCancelButton = nullptr; progressCancelObject = nullptr; detailsWidget = nullptr; correlatorWidget = nullptr; rgWidget = nullptr; searchWidget = nullptr; kmlWidget = nullptr; mapSplitter = nullptr; mapWidget = nullptr; mapWidget2 = nullptr; mapDragDropHandler = nullptr; mapModelHelper = nullptr; geoifaceMarkerModel = nullptr; sortActionOldestFirst = nullptr; sortActionYoungestFirst = nullptr; sortMenu = nullptr; mapLayout = MapLayoutOne; cbMapLayout = nullptr; bookmarkOwner = nullptr; actionBookmarkVisibility = nullptr; iface = nullptr; } // General things GPSItemModel* imageModel; QItemSelectionModel* selectionModel; bool uiEnabled; GPSItemListContextMenu* listViewContextMenu; TrackManager* trackManager; // Loading and saving QFuture > fileIOFuture; QFutureWatcher >* fileIOFutureWatcher; int fileIOCountDone; int fileIOCountTotal; bool fileIOCloseAfterSaving; // UI QDialogButtonBox* buttonBox; QSplitter* VSplitter; QSplitter* HSplitter; GPSItemList* treeView; QStackedWidget* stackedWidget; QTabBar* tabBar; int splitterSize; QUndoStack* undoStack; QUndoView* undoView; // UI: progress StatusProgressBar* progressBar; QPushButton* progressCancelButton; QObject* progressCancelObject; QString progressCancelSlot; // UI: tab widgets GPSItemDetails* detailsWidget; GPSCorrelatorWidget* correlatorWidget; RGWidget* rgWidget; SearchWidget* searchWidget; KmlWidget* kmlWidget; // map: UI MapLayout mapLayout; QSplitter* mapSplitter; MapWidget* mapWidget; MapWidget* mapWidget2; // map: helpers MapDragDropHandler* mapDragDropHandler; GPSGeoIfaceModelHelper* mapModelHelper; ItemMarkerTiler* geoifaceMarkerModel; // map: actions QAction* sortActionOldestFirst; QAction* sortActionYoungestFirst; QMenu* sortMenu; QComboBox* cbMapLayout; GPSBookmarkOwner* bookmarkOwner; QAction* actionBookmarkVisibility; DInfoInterface* iface; }; GeolocationEdit::GeolocationEdit(QWidget* const parent, DInfoInterface* const iface) : DPluginDialog(parent, QLatin1String("Geolocation Edit Settings")), d(new Private) { setAttribute(Qt::WA_DeleteOnClose, true); setWindowTitle(i18n("Geolocation Editor")); setMinimumSize(300, 400); setModal(true); d->iface = iface; d->imageModel = new GPSItemModel(this); d->selectionModel = new QItemSelectionModel(d->imageModel); d->trackManager = new TrackManager(this); #ifdef GPSSYNC_MODELTEST new ModelTest(d->imageModel, this); #endif d->bookmarkOwner = new GPSBookmarkOwner(d->imageModel, this); d->undoStack = new QUndoStack(this); d->stackedWidget = new QStackedWidget(); d->searchWidget = new SearchWidget(d->bookmarkOwner, d->imageModel, d->selectionModel, d->stackedWidget); GPSItemContainer::setHeaderData(d->imageModel); d->mapModelHelper = new GPSGeoIfaceModelHelper(d->imageModel, d->selectionModel, this); d->mapModelHelper->addUngroupedModelHelper(d->bookmarkOwner->bookmarkModelHelper()); d->mapModelHelper->addUngroupedModelHelper(d->searchWidget->getModelHelper()); d->mapDragDropHandler = new MapDragDropHandler(d->imageModel, d->mapModelHelper); d->geoifaceMarkerModel = new ItemMarkerTiler(d->mapModelHelper, this); d->actionBookmarkVisibility = new QAction(this); d->actionBookmarkVisibility->setIcon(QIcon::fromTheme(QLatin1String("bookmark-new"))); d->actionBookmarkVisibility->setToolTip(i18n("Display bookmarked positions on the map.")); d->actionBookmarkVisibility->setCheckable(true); connect(d->actionBookmarkVisibility, SIGNAL(changed()), this, SLOT(slotBookmarkVisibilityToggled())); QVBoxLayout* const mainLayout = new QVBoxLayout(this); setLayout(mainLayout); DHBox* const hboxMain = new DHBox(this); mainLayout->addWidget(hboxMain, 10); d->HSplitter = new QSplitter(Qt::Horizontal, hboxMain); d->HSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // ------------------------------------------------------------------------------------------------ DHBox* const hbox = new DHBox(this); QLabel* const labelMapLayout = new QLabel(i18n("Layout:"), hbox); d->cbMapLayout = new QComboBox(hbox); d->cbMapLayout->addItem(i18n("One map"), QVariant::fromValue(MapLayoutOne)); d->cbMapLayout->addItem(i18n("Two maps - horizontal"), QVariant::fromValue(MapLayoutHorizontal)); d->cbMapLayout->addItem(i18n("Two maps - vertical"), QVariant::fromValue(MapLayoutVertical)); labelMapLayout->setBuddy(d->cbMapLayout); d->progressBar = new StatusProgressBar(hbox); d->progressBar->setVisible(false); d->progressBar->setProgressBarMode(StatusProgressBar::ProgressBarMode); d->progressBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); d->progressCancelButton = new QPushButton(hbox); d->progressCancelButton->setVisible(false); d->progressCancelButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); d->progressCancelButton->setIcon(QIcon::fromTheme(QLatin1String("dialog-cancel"))); connect(d->progressCancelButton, SIGNAL(clicked()), this, SLOT(slotProgressCancelButtonClicked())); m_buttons->addButton(QDialogButtonBox::Apply); m_buttons->addButton(QDialogButtonBox::Close); m_buttons->setParent(hbox); connect(m_buttons->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &GeolocationEdit::slotApplyClicked); connect(m_buttons->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &GeolocationEdit::close); mainLayout->addWidget(hbox, 0); // ------------------------------------------------------------------------------------------------ d->VSplitter = new QSplitter(Qt::Vertical, d->HSplitter); d->HSplitter->addWidget(d->VSplitter); d->HSplitter->setStretchFactor(0, 10); d->sortMenu = new QMenu(this); d->sortMenu->setTitle(i18n("Sorting")); QActionGroup* const sortOrderExclusive = new QActionGroup(d->sortMenu); sortOrderExclusive->setExclusive(true); connect(sortOrderExclusive, SIGNAL(triggered(QAction*)), this, SLOT(slotSortOptionTriggered(QAction*))); d->sortActionOldestFirst = new QAction(i18n("Show oldest first"), sortOrderExclusive); d->sortActionOldestFirst->setCheckable(true); d->sortMenu->addAction(d->sortActionOldestFirst); d->sortActionYoungestFirst = new QAction(i18n("Show youngest first"), sortOrderExclusive); d->sortMenu->addAction(d->sortActionYoungestFirst); d->sortActionYoungestFirst->setCheckable(true); QWidget* mapVBox = nullptr; d->mapWidget = makeMapWidget(&mapVBox); d->searchWidget->setPrimaryMapWidget(d->mapWidget); d->mapSplitter = new QSplitter(this); d->mapSplitter->addWidget(mapVBox); d->VSplitter->addWidget(d->mapSplitter); d->treeView = new GPSItemList(this); d->treeView->setModelAndSelectionModel(d->imageModel, d->selectionModel); d->treeView->setDragDropHandler(new GPSItemListDragDropHandler(this)); d->treeView->setDragEnabled(true); // TODO: save and restore the state of the header // TODO: add a context menu to the header to select which columns should be visible // TODO: add sorting by column d->treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); d->treeView->setSortingEnabled(true); d->VSplitter->addWidget(d->treeView); d->listViewContextMenu = new GPSItemListContextMenu(d->treeView, d->bookmarkOwner); d->HSplitter->addWidget(d->stackedWidget); d->HSplitter->setCollapsible(1, true); d->splitterSize = 0; DVBox* const vboxTabBar = new DVBox(hboxMain); vboxTabBar->layout()->setContentsMargins(QMargins()); vboxTabBar->layout()->setSpacing(0); d->tabBar = new QTabBar(vboxTabBar); d->tabBar->setShape(QTabBar::RoundedEast); dynamic_cast(vboxTabBar->layout())->addStretch(200); d->tabBar->addTab(i18n("Details")); d->tabBar->addTab(i18n("GPS Correlator")); d->tabBar->addTab(i18n("Undo/Redo")); d->tabBar->addTab(i18n("Reverse Geocoding")); d->tabBar->addTab(i18n("Search")); d->tabBar->addTab(i18n("KML Export")); d->tabBar->installEventFilter(this); d->detailsWidget = new GPSItemDetails(d->stackedWidget, d->imageModel); d->stackedWidget->addWidget(d->detailsWidget); d->correlatorWidget = new GPSCorrelatorWidget(d->stackedWidget, d->imageModel, d->trackManager); d->stackedWidget->addWidget(d->correlatorWidget); d->undoView = new QUndoView(d->undoStack, d->stackedWidget); d->stackedWidget->addWidget(d->undoView); d->rgWidget = new RGWidget(d->imageModel, d->selectionModel, d->iface->tagFilterModel(), d->stackedWidget); d->stackedWidget->addWidget(d->rgWidget); d->stackedWidget->addWidget(d->searchWidget); d->kmlWidget = new KmlWidget(this, d->imageModel, d->iface); d->stackedWidget->addWidget(d->kmlWidget); // --------------------------------------------------------------- connect(d->treeView, SIGNAL(signalImageActivated(QModelIndex)), this, SLOT(slotImageActivated(QModelIndex))); connect(d->correlatorWidget, SIGNAL(signalSetUIEnabled(bool)), this, SLOT(slotSetUIEnabled(bool))); connect(d->correlatorWidget, SIGNAL(signalSetUIEnabled(bool,QObject*const,QString)), this, SLOT(slotSetUIEnabled(bool,QObject*const,QString))); connect(d->correlatorWidget, SIGNAL(signalProgressSetup(int,QString)), this, SLOT(slotProgressSetup(int,QString))); connect(d->correlatorWidget, SIGNAL(signalProgressChanged(int)), this, SLOT(slotProgressChanged(int))); connect(d->correlatorWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->mapModelHelper, SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->rgWidget, SIGNAL(signalSetUIEnabled(bool)), this, SLOT(slotSetUIEnabled(bool))); connect(d->rgWidget, SIGNAL(signalSetUIEnabled(bool,QObject*const,QString)), this, SLOT(slotSetUIEnabled(bool,QObject*const,QString))); connect(d->rgWidget, SIGNAL(signalProgressSetup(int,QString)), this, SLOT(slotProgressSetup(int,QString))); connect(d->rgWidget, SIGNAL(signalProgressChanged(int)), this, SLOT(slotProgressChanged(int))); connect(d->rgWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->searchWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->listViewContextMenu, SIGNAL(signalSetUIEnabled(bool)), this, SLOT(slotSetUIEnabled(bool))); connect(d->listViewContextMenu, SIGNAL(signalSetUIEnabled(bool,QObject*const,QString)), this, SLOT(slotSetUIEnabled(bool,QObject*const,QString))); connect(d->listViewContextMenu, SIGNAL(signalProgressSetup(int,QString)), this, SLOT(slotProgressSetup(int,QString))); connect(d->listViewContextMenu, SIGNAL(signalProgressChanged(int)), this, SLOT(slotProgressChanged(int))); connect(d->listViewContextMenu, SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->tabBar, SIGNAL(currentChanged(int)), this, SLOT(slotCurrentTabChanged(int))); connect(d->bookmarkOwner->bookmarkModelHelper(), SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->detailsWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->cbMapLayout, SIGNAL(activated(int)), this, SLOT(slotLayoutChanged(int))); connect(this, SIGNAL(signalMetadataChangedForUrl(QUrl)), d->iface, SLOT(slotMetadataChangedForUrl(QUrl))); readSettings(); d->mapWidget->setActive(true); setItems(d->iface->currentGPSItems()); } GeolocationEdit::~GeolocationEdit() { + delete d->bookmarkOwner; delete d; } bool GeolocationEdit::eventFilter(QObject* const o, QEvent* const e) { if ((o == d->tabBar) && (e->type() == QEvent::MouseButtonPress)) { QMouseEvent const* m = static_cast(e); QPoint p (m->x(), m->y()); const int var = d->tabBar->tabAt(p); if (var < 0) { return false; } QList sizes = d->HSplitter->sizes(); if (d->splitterSize == 0) { if (sizes.at(1) == 0) { sizes[1] = d->stackedWidget->widget(var)->minimumSizeHint().width(); } else if (d->tabBar->currentIndex() == var) { d->splitterSize = sizes.at(1); sizes[1] = 0; } } else { sizes[1] = d->splitterSize; d->splitterSize = 0; } d->tabBar->setCurrentIndex(var); d->stackedWidget->setCurrentIndex(var); d->HSplitter->setSizes(sizes); d->detailsWidget->slotSetActive((d->stackedWidget->currentWidget() == d->detailsWidget) && (d->splitterSize == 0)); return true; } return QWidget::eventFilter(o, e); } void GeolocationEdit::slotCurrentTabChanged(int index) { d->tabBar->setCurrentIndex(index); d->stackedWidget->setCurrentIndex(index); d->detailsWidget->slotSetActive(d->stackedWidget->currentWidget() == d->detailsWidget); } void GeolocationEdit::setCurrentTab(int index) { d->tabBar->setCurrentIndex(index); d->stackedWidget->setCurrentIndex(index); QList sizes = d->HSplitter->sizes(); if (d->splitterSize >= 0) { sizes[1] = d->splitterSize; d->splitterSize = 0; } d->HSplitter->setSizes(sizes); d->detailsWidget->slotSetActive((d->stackedWidget->currentWidget() == d->detailsWidget) && (d->splitterSize == 0)); } void GeolocationEdit::setImages(const QList& images) { QList items; foreach (const QUrl& u, images) { items << new GPSItemContainer(u); } setItems(items); } void GeolocationEdit::setItems(const QList& items) { foreach (GPSItemContainer* const newItem, items) { newItem->loadImageData(); d->imageModel->addItem(newItem); } QList imagesToLoad; for (int i = 0 ; i < d->imageModel->rowCount() ; ++i) { imagesToLoad << d->imageModel->index(i, 0); } slotSetUIEnabled(false); slotProgressSetup(imagesToLoad.count(), i18n("Loading metadata -")); // initiate the saving d->fileIOCountDone = 0; d->fileIOCountTotal = imagesToLoad.count(); d->fileIOFutureWatcher = new QFutureWatcher >(this); connect(d->fileIOFutureWatcher, SIGNAL(resultsReadyAt(int,int)), this, SLOT(slotFileMetadataLoaded(int,int))); d->fileIOFuture = QtConcurrent::mapped(imagesToLoad, LoadFileMetadataHelper(d->imageModel)); d->fileIOFutureWatcher->setFuture(d->fileIOFuture); } void GeolocationEdit::slotFileMetadataLoaded(int beginIndex, int endIndex) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << beginIndex << endIndex; d->fileIOCountDone += (endIndex-beginIndex); slotProgressChanged(d->fileIOCountDone); if (d->fileIOCountDone == d->fileIOCountTotal) { slotSetUIEnabled(true); } } void GeolocationEdit::readSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("Geolocation Edit Settings"); // -------------------------- // TODO: sanely determine a default backend const KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget"); d->mapWidget->readSettingsFromGroup(&groupMapWidget); const KConfigGroup groupCorrelatorWidget = KConfigGroup(&group, "Correlator Widget"); d->correlatorWidget->readSettingsFromGroup(&groupCorrelatorWidget); const KConfigGroup groupTreeView = KConfigGroup(&group, "Tree View"); d->treeView->readSettingsFromGroup(&groupTreeView); const KConfigGroup groupSearchWidget = KConfigGroup(&group, "Search Widget"); d->searchWidget->readSettingsFromGroup(&groupSearchWidget); const KConfigGroup groupRGWidget = KConfigGroup(&group, "Reverse Geocoding Widget"); d->rgWidget->readSettingsFromGroup(&groupRGWidget); const KConfigGroup groupDialog = KConfigGroup(&group, "Dialog"); // -------------------------- setCurrentTab(group.readEntry("Current Tab", 0)); const bool showOldestFirst = group.readEntry("Show oldest images first", false); if (showOldestFirst) { d->sortActionOldestFirst->setChecked(true); d->mapWidget->setSortKey(1); } else { d->sortActionYoungestFirst->setChecked(true); d->mapWidget->setSortKey(0); } d->actionBookmarkVisibility->setChecked(group.readEntry("Bookmarks visible", false)); slotBookmarkVisibilityToggled(); if (group.hasKey("SplitterState V1")) { const QByteArray splitterState = QByteArray::fromBase64(group.readEntry("SplitterState V1", QByteArray())); if (!splitterState.isEmpty()) { d->VSplitter->restoreState(splitterState); } } if (group.hasKey("SplitterState H1")) { const QByteArray splitterState = QByteArray::fromBase64(group.readEntry("SplitterState H1", QByteArray())); if (!splitterState.isEmpty()) { d->HSplitter->restoreState(splitterState); } } d->splitterSize = group.readEntry("Splitter H1 CollapsedSize", 0); // ---------------------------------- d->mapLayout = MapLayout(group.readEntry("Map Layout", QVariant::fromValue(int(MapLayoutOne))).value()); d->cbMapLayout->setCurrentIndex(d->mapLayout); adjustMapLayout(false); if (d->mapWidget2) { const KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget 2"); d->mapWidget2->readSettingsFromGroup(&groupMapWidget); d->mapWidget2->setActive(true); } } void GeolocationEdit::saveSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("Geolocation Edit Settings"); // -------------------------- KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget"); d->mapWidget->saveSettingsToGroup(&groupMapWidget); if (d->mapWidget2) { KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget 2"); d->mapWidget2->saveSettingsToGroup(&groupMapWidget); } KConfigGroup groupCorrelatorWidget = KConfigGroup(&group, "Correlator Widget"); d->correlatorWidget->saveSettingsToGroup(&groupCorrelatorWidget); KConfigGroup groupTreeView = KConfigGroup(&group, "Tree View"); d->treeView->saveSettingsToGroup(&groupTreeView); KConfigGroup groupSearchWidget = KConfigGroup(&group, "Search Widget"); d->searchWidget->saveSettingsToGroup(&groupSearchWidget); KConfigGroup groupRGWidget = KConfigGroup(&group, "Reverse Geocoding Widget"); d->rgWidget->saveSettingsToGroup(&groupRGWidget); // -------------------------- group.writeEntry("Current Tab", d->tabBar->currentIndex()); group.writeEntry("Show oldest images first", d->sortActionOldestFirst->isChecked()); group.writeEntry("SplitterState V1", d->VSplitter->saveState().toBase64()); group.writeEntry("SplitterState H1", d->HSplitter->saveState().toBase64()); group.writeEntry("Splitter H1 CollapsedSize", d->splitterSize); group.writeEntry("Map Layout", QVariant::fromValue(int(d->mapLayout))); group.writeEntry("Bookmarks visible", d->actionBookmarkVisibility->isChecked()); config->sync(); } void GeolocationEdit::closeEvent(QCloseEvent *e) { if (!e) return; // is the UI locked? if (!d->uiEnabled) { // please wait until we are done ... return; } // are there any modified images? int dirtyImagesCount = 0; for (int i = 0 ; i < d->imageModel->rowCount() ; ++i) { const QModelIndex itemIndex = d->imageModel->index(i, 0); GPSItemContainer* const item = d->imageModel->itemFromIndex(itemIndex); if (item->isDirty() || item->isTagListDirty()) { ++dirtyImagesCount; } } if (dirtyImagesCount > 0) { const QString message = i18np( "You have 1 modified image.", "You have %1 modified images.", dirtyImagesCount ); const int chosenAction = DMessageBox::showYesNo(QMessageBox::Warning, this, i18n("Unsaved changes"), i18n("%1 Would you like to save the changes you made to them?", message) ); if (chosenAction == QMessageBox::No) { saveSettings(); e->accept(); return; } if (chosenAction == QMessageBox::Yes) { // the user wants to save his changes. // this will initiate the saving process and then close the dialog. saveChanges(true); } // do not close the dialog for now e->ignore(); return; } saveSettings(); e->accept(); } void GeolocationEdit::slotImageActivated(const QModelIndex& index) { d->detailsWidget->slotSetCurrentImage(index); if (!index.isValid()) return; GPSItemContainer* const item = d->imageModel->itemFromIndex(index); if (!item) return; const GeoCoordinates imageCoordinates = item->coordinates(); if (imageCoordinates.hasCoordinates()) { d->mapWidget->setCenter(imageCoordinates); } } void GeolocationEdit::slotSetUIEnabled(const bool enabledState, QObject* const cancelObject, const QString& cancelSlot) { if (enabledState) { // hide the progress bar d->progressBar->setVisible(false); d->progressCancelButton->setVisible(false); d->progressBar->setProgressValue(d->progressBar->progressTotalSteps()); } // TODO: disable the worldmapwidget and the images list (at least disable editing operations) d->progressCancelObject = cancelObject; d->progressCancelSlot = cancelSlot; d->uiEnabled = enabledState; m_buttons->setEnabled(enabledState); d->correlatorWidget->setUIEnabledExternal(enabledState); d->detailsWidget->setUIEnabledExternal(enabledState); d->rgWidget->setUIEnabled(enabledState); d->treeView->setEditEnabled(enabledState); d->listViewContextMenu->setEnabled(enabledState); d->mapWidget->setAllowModifications(enabledState); } void GeolocationEdit::slotSetUIEnabled(const bool enabledState) { slotSetUIEnabled(enabledState, nullptr, QString()); } void GeolocationEdit::saveChanges(const bool closeAfterwards) { // TODO: actually save the changes // are there any modified images? QList dirtyImages; for (int i = 0 ; i < d->imageModel->rowCount() ; ++i) { const QModelIndex itemIndex = d->imageModel->index(i, 0); GPSItemContainer* const item = d->imageModel->itemFromIndex(itemIndex); if (item->isDirty() || item->isTagListDirty()) { dirtyImages << itemIndex; } } if (dirtyImages.isEmpty()) { if (closeAfterwards) { close(); } return; } // TODO: disable the UI and provide progress and cancel information slotSetUIEnabled(false); slotProgressSetup(dirtyImages.count(), i18n("Saving changes -")); // initiate the saving d->fileIOCountDone = 0; d->fileIOCountTotal = dirtyImages.count(); d->fileIOCloseAfterSaving = closeAfterwards; d->fileIOFutureWatcher = new QFutureWatcher >(this); connect(d->fileIOFutureWatcher, SIGNAL(resultsReadyAt(int,int)), this, SLOT(slotFileChangesSaved(int,int))); d->fileIOFuture = QtConcurrent::mapped(dirtyImages, SaveChangedImagesHelper(d->imageModel)); d->fileIOFutureWatcher->setFuture(d->fileIOFuture); } void GeolocationEdit::slotFileChangesSaved(int beginIndex, int endIndex) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << beginIndex << endIndex; d->fileIOCountDone += (endIndex-beginIndex); slotProgressChanged(d->fileIOCountDone); if (d->fileIOCountDone == d->fileIOCountTotal) { slotSetUIEnabled(true); // any errors? QList > errorList; for (int i = 0 ; i < d->fileIOFuture.resultCount() ; ++i) { if (!d->fileIOFuture.resultAt(i).second.isEmpty()) errorList << d->fileIOFuture.resultAt(i); // To rescan item metadata from host. emit signalMetadataChangedForUrl(d->fileIOFuture.resultAt(i).first); } if (!errorList.isEmpty()) { QStringList errorStrings; for (int i = 0 ; i < errorList.count() ; ++i) { errorStrings << QString::fromLatin1("%1: %2") .arg(errorList.at(i).first.toLocalFile()) .arg(errorList.at(i).second); } DMessageBox::showInformationList(QMessageBox::Critical, this, i18n("Error"), i18n("Failed to save some information:"), errorStrings); } // done saving files if (d->fileIOCloseAfterSaving) { close(); } } } void GeolocationEdit::slotApplyClicked() { // save the changes, but do not close afterwards saveChanges(false); } void GeolocationEdit::slotProgressChanged(const int currentProgress) { d->progressBar->setProgressValue(currentProgress); } void GeolocationEdit::slotProgressSetup(const int maxProgress, const QString& progressText) { d->progressBar->setProgressText(progressText); d->progressBar->setProgressTotalSteps(maxProgress); d->progressBar->setProgressValue(0); d->progressBar->setNotify(true); d->progressBar->setNotificationTitle(i18n("Edit Geolocation"), QIcon::fromTheme(QLatin1String("globe"))); d->progressBar->setVisible(true); d->progressCancelButton->setVisible(d->progressCancelObject != nullptr); } void GeolocationEdit::slotGPSUndoCommand(GPSUndoCommand* undoCommand) { d->undoStack->push(undoCommand); } void GeolocationEdit::slotSortOptionTriggered(QAction* /*sortAction*/) { int newSortKey = 0; if (d->sortActionOldestFirst->isChecked()) { newSortKey |= 1; } d->mapWidget->setSortKey(newSortKey); } void GeolocationEdit::slotProgressCancelButtonClicked() { if (d->progressCancelObject) { QTimer::singleShot(0, d->progressCancelObject, d->progressCancelSlot.toUtf8().constData()); d->progressBar->setProgressValue(d->progressBar->progressTotalSteps()); } } void GeolocationEdit::slotBookmarkVisibilityToggled() { d->bookmarkOwner->bookmarkModelHelper()->setVisible(d->actionBookmarkVisibility->isChecked()); } void GeolocationEdit::slotLayoutChanged(int lay) { d->mapLayout = (MapLayout)lay; adjustMapLayout(true); } MapWidget* GeolocationEdit::makeMapWidget(QWidget** const pvbox) { QWidget* const dummyWidget = new QWidget(this); QVBoxLayout* const vbox = new QVBoxLayout(dummyWidget); MapWidget* const mapWidget = new MapWidget(dummyWidget); mapWidget->setAvailableMouseModes(MouseModePan | MouseModeZoomIntoGroup | MouseModeSelectThumbnail); mapWidget->setVisibleMouseModes(MouseModePan | MouseModeZoomIntoGroup | MouseModeSelectThumbnail); mapWidget->setMouseMode(MouseModeSelectThumbnail); mapWidget->setGroupedModel(d->geoifaceMarkerModel); mapWidget->setDragDropHandler(d->mapDragDropHandler); mapWidget->addUngroupedModel(d->bookmarkOwner->bookmarkModelHelper()); mapWidget->addUngroupedModel(d->searchWidget->getModelHelper()); mapWidget->setTrackManager(d->trackManager); mapWidget->setSortOptionsMenu(d->sortMenu); vbox->addWidget(mapWidget); vbox->addWidget(mapWidget->getControlWidget()); QToolButton* const bookmarkVisibilityButton = new QToolButton(mapWidget); bookmarkVisibilityButton->setDefaultAction(d->actionBookmarkVisibility); mapWidget->addWidgetToControlWidget(bookmarkVisibilityButton); *pvbox = dummyWidget; return mapWidget; } void GeolocationEdit::adjustMapLayout(const bool syncSettings) { if (d->mapLayout == MapLayoutOne) { if (d->mapSplitter->count() > 1) { delete d->mapSplitter->widget(1); d->mapWidget2 = nullptr; } } else { if (d->mapSplitter->count() == 1) { QWidget* mapHolder = nullptr; d->mapWidget2 = makeMapWidget(&mapHolder); d->mapSplitter->addWidget(mapHolder); if (syncSettings) { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("Geolocation Edit Settings"); const KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget"); d->mapWidget2->readSettingsFromGroup(&groupMapWidget); d->mapWidget2->setActive(true); } } if (d->mapLayout == MapLayoutHorizontal) { d->mapSplitter->setOrientation(Qt::Horizontal); } else { d->mapSplitter->setOrientation(Qt::Vertical); } } } } // namespace DigikamGenericGeolocationEditPlugin diff --git a/core/utilities/geolocation/geoiface/bookmark/bookmarknode.cpp b/core/utilities/geolocation/geoiface/bookmark/bookmarknode.cpp index 1089b15dea..16392c7f70 100644 --- a/core/utilities/geolocation/geoiface/bookmark/bookmarknode.cpp +++ b/core/utilities/geolocation/geoiface/bookmark/bookmarknode.cpp @@ -1,416 +1,415 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-05-15 * Description : a node container for GPS bookmarks * * Copyright (C) 2017-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 "bookmarknode.h" // Qt includes #include #include // KDE includes #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN BookmarkNode::Private { public: explicit Private() : parent(nullptr), type(BookmarkNode::Root) { } BookmarkNode* parent; Type type; QList children; }; BookmarkNode::BookmarkNode(BookmarkNode::Type type, BookmarkNode* const parent) : QObject(nullptr), d(new Private) { - qDebug() << "Created"; expanded = false; d->parent = parent; d->type = type; if (parent) { parent->add(this); } } BookmarkNode::~BookmarkNode() { if (d->parent) { d->parent->remove(this); } -qDebug() << "Deleted"; + qDeleteAll(d->children); d->parent = nullptr; d->type = BookmarkNode::Root; delete d; } bool BookmarkNode::operator==(const BookmarkNode& other) const { if (url != other.url || title != other.title || desc != other.desc || expanded != other.expanded || dateAdded != other.dateAdded || d->type != other.d->type || d->children.count() != other.d->children.count()) { return false; } for (int i = 0 ; i < d->children.count() ; ++i) { if (!((*(d->children[i])) == (*(other.d->children[i])))) { return false; } } return true; } BookmarkNode::Type BookmarkNode::type() const { return d->type; } void BookmarkNode::setType(Type type) { d->type = type; } QList BookmarkNode::children() const { return d->children; } BookmarkNode* BookmarkNode::parent() const { return d->parent; } void BookmarkNode::add(BookmarkNode* const child, int offset) { Q_ASSERT(child->d->type != Root); if (child->d->parent) { child->d->parent->remove(child); } child->d->parent = this; if (offset == -1) { offset = d->children.size(); } d->children.insert(offset, child); } void BookmarkNode::remove(BookmarkNode* const child) { child->d->parent = nullptr; d->children.removeAll(child); } // ------------------------------------------------------- XbelReader::XbelReader() { } BookmarkNode* XbelReader::read(const QString& fileName) { QFile file(fileName); if (!file.exists() || !file.open(QFile::ReadOnly)) { BookmarkNode* const root = new BookmarkNode(BookmarkNode::Root); BookmarkNode* const folder = new BookmarkNode(BookmarkNode::RootFolder, root); folder->title = i18n("Bookmark folder"); return root; } return read(&file, true); } BookmarkNode* XbelReader::read(QIODevice* const device, bool addRootFolder) { BookmarkNode* const root = new BookmarkNode(BookmarkNode::Root); setDevice(device); if (readNextStartElement()) { QString version = attributes().value(QLatin1String("version")).toString(); if ((name() == QLatin1String("xbel")) && (version.isEmpty() || version == QLatin1String("1.0"))) { if (addRootFolder) { BookmarkNode* const folder = new BookmarkNode(BookmarkNode::RootFolder, root); folder->title = i18n("Bookmark folder"); readXBEL(folder); } else { readXBEL(root); } } else { raiseError(i18n("The file is not an XBEL version 1.0 file.")); } } return root; } void XbelReader::readXBEL(BookmarkNode* const parent) { Q_ASSERT(isStartElement() && name() == QLatin1String("xbel")); while (readNextStartElement()) { if (name() == QLatin1String("folder")) { readFolder(parent); } else if (name() == QLatin1String("bookmark")) { readBookmarkNode(parent); } else if (name() == QLatin1String("separator")) { readSeparator(parent); } else { skipCurrentElement(); } } } void XbelReader::readFolder(BookmarkNode* const parent) { Q_ASSERT(isStartElement() && name() == QLatin1String("folder")); BookmarkNode* const folder = new BookmarkNode(BookmarkNode::Folder, parent); folder->expanded = (attributes().value(QLatin1String("folded")) == QLatin1String("no")); while (readNextStartElement()) { if (name() == QLatin1String("title")) { readTitle(folder); } else if (name() == QLatin1String("desc")) { readDescription(folder); } else if (name() == QLatin1String("folder")) { readFolder(folder); } else if (name() == QLatin1String("bookmark")) { readBookmarkNode(folder); } else if (name() == QLatin1String("separator")) { readSeparator(folder); } else { skipCurrentElement(); } } } void XbelReader::readTitle(BookmarkNode* const parent) { Q_ASSERT(isStartElement() && name() == QLatin1String("title")); parent->title = readElementText(); } void XbelReader::readDescription(BookmarkNode* const parent) { Q_ASSERT(isStartElement() && name() == QLatin1String("desc")); parent->desc = readElementText(); } void XbelReader::readSeparator(BookmarkNode* const parent) { new BookmarkNode(BookmarkNode::Separator, parent); // empty elements have a start and end element readNext(); } void XbelReader::readBookmarkNode(BookmarkNode* const parent) { Q_ASSERT(isStartElement() && name() == QLatin1String("bookmark")); BookmarkNode* const bookmark = new BookmarkNode(BookmarkNode::Bookmark, parent); bookmark->url = attributes().value(QLatin1String("href")).toString(); QString date = attributes().value(QLatin1String("added")).toString(); bookmark->dateAdded = QDateTime::fromString(date, Qt::ISODate); while (readNextStartElement()) { if (name() == QLatin1String("title")) { readTitle(bookmark); } else if (name() == QLatin1String("desc")) { readDescription(bookmark); } else { skipCurrentElement(); } } if (bookmark->title.isEmpty()) { bookmark->title = i18n("Unknown title"); } } // ------------------------------------------------------- XbelWriter::XbelWriter() { setAutoFormatting(true); } bool XbelWriter::write(const QString& fileName, const BookmarkNode* const root) { QFile file(fileName); if (!root || !file.open(QFile::WriteOnly)) { return false; } return write(&file, root); } bool XbelWriter::write(QIODevice* const device, const BookmarkNode* const root) { setDevice(device); writeStartDocument(); writeDTD(QLatin1String("")); writeStartElement(QLatin1String("xbel")); writeAttribute(QLatin1String("version"), QLatin1String("1.0")); if (root->type() == BookmarkNode::Root) { BookmarkNode* const rootFolder = root->children().first(); for (int i = 0 ; i < rootFolder->children().count() ; ++i) { writeItem(rootFolder->children().at(i)); } } else { writeItem(root); } writeEndDocument(); return true; } void XbelWriter::writeItem(const BookmarkNode* const parent) { switch (parent->type()) { case BookmarkNode::Folder: writeStartElement(QLatin1String("folder")); writeAttribute(QLatin1String("folded"), parent->expanded ? QLatin1String("no") : QLatin1String("yes")); writeTextElement(QLatin1String("title"), parent->title); for (int i = 0 ; i < parent->children().count() ; ++i) { writeItem(parent->children().at(i)); } writeEndElement(); break; case BookmarkNode::Bookmark: writeStartElement(QLatin1String("bookmark")); if (!parent->url.isEmpty()) { writeAttribute(QLatin1String("href"), parent->url); } if (parent->dateAdded.isValid()) { writeAttribute(QLatin1String("added"), parent->dateAdded.toString(Qt::ISODate)); } if (!parent->desc.isEmpty()) { writeAttribute(QLatin1String("desc"), parent->desc); } writeTextElement(QLatin1String("title"), parent->title); writeEndElement(); break; case BookmarkNode::Separator: writeEmptyElement(QLatin1String("separator")); break; default: break; } } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/bookmark/bookmarksmngr.cpp b/core/utilities/geolocation/geoiface/bookmark/bookmarksmngr.cpp index da95b2a375..b42b0dc83a 100644 --- a/core/utilities/geolocation/geoiface/bookmark/bookmarksmngr.cpp +++ b/core/utilities/geolocation/geoiface/bookmark/bookmarksmngr.cpp @@ -1,848 +1,849 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-05-15 * Description : low level manager for GPS bookmarks * * Copyright (C) 2017-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 "bookmarksmngr.h" // Qt includes #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include #include "bookmarknode.h" #include "digikam_debug.h" namespace Digikam { RemoveBookmarksCommand::RemoveBookmarksCommand(BookmarksManager* const mngr, BookmarkNode* const parent, int row) : QUndoCommand(i18n("Remove Bookmark")), m_row(row), m_bookmarkManager(mngr), m_node(parent->children().value(row)), m_parent(parent), m_done(false) { } RemoveBookmarksCommand::~RemoveBookmarksCommand() { if (m_done && !m_node->parent()) { delete m_node; } } void RemoveBookmarksCommand::undo() { m_parent->add(m_node, m_row); emit m_bookmarkManager->entryAdded(m_node); m_done = false; } void RemoveBookmarksCommand::redo() { m_parent->remove(m_node); emit m_bookmarkManager->entryRemoved(m_parent, m_row, m_node); m_done = true; } // -------------------------------------------------------------- InsertBookmarksCommand::InsertBookmarksCommand(BookmarksManager* const mngr, BookmarkNode* const parent, BookmarkNode* const node, int row) : RemoveBookmarksCommand(mngr, parent, row) { setText(i18n("Insert Bookmark")); m_node = node; } void InsertBookmarksCommand::undo() { RemoveBookmarksCommand::redo(); } void InsertBookmarksCommand::redo() { RemoveBookmarksCommand::undo(); } // -------------------------------------------------------------- class Q_DECL_HIDDEN ChangeBookmarkCommand::Private { public: explicit Private() : manager(nullptr), type(Url), node(nullptr) { } BookmarksManager* manager; BookmarkData type; QString oldValue; QString newValue; BookmarkNode* node; }; ChangeBookmarkCommand::ChangeBookmarkCommand(BookmarksManager* const mngr, BookmarkNode* const node, const QString& newValue, BookmarkData type) : QUndoCommand(), d(new Private) { d->manager = mngr; d->type = type; d->newValue = newValue; d->node = node; switch (d->type) { case Title: d->oldValue = d->node->title; setText(i18n("Title Change")); break; case Desc: d->oldValue = d->node->desc; setText(i18n("Comment Change")); break; default: // Url d->oldValue = d->node->url; setText(i18n("Address Change")); break; } } ChangeBookmarkCommand::~ChangeBookmarkCommand() { delete d; } void ChangeBookmarkCommand::undo() { switch (d->type) { case Title: d->node->title = d->oldValue; break; case Desc: d->node->desc = d->oldValue; break; default: // Url d->node->url = d->oldValue; break; } emit d->manager->entryChanged(d->node); } void ChangeBookmarkCommand::redo() { switch (d->type) { case Title: d->node->title = d->newValue; break; case Desc: d->node->desc = d->newValue; break; default: // Url d->node->url = d->newValue; break; } emit d->manager->entryChanged(d->node); } // -------------------------------------------------------------- class Q_DECL_HIDDEN BookmarksModel::Private { public: explicit Private() : manager(nullptr), endMacro(false) { } BookmarksManager* manager; bool endMacro; }; BookmarksModel::BookmarksModel(BookmarksManager* const mngr, QObject* const parent) : QAbstractItemModel(parent), d(new Private) { d->manager = mngr; connect(d->manager, SIGNAL(entryAdded(BookmarkNode*)), this, SLOT(entryAdded(BookmarkNode*))); connect(d->manager, SIGNAL(entryRemoved(BookmarkNode*,int,BookmarkNode*)), this, SLOT(entryRemoved(BookmarkNode*,int,BookmarkNode*))); connect(d->manager, SIGNAL(entryChanged(BookmarkNode*)), this, SLOT(entryChanged(BookmarkNode*))); } BookmarksModel::~BookmarksModel() { delete d; } BookmarksManager* BookmarksModel::bookmarksManager() const { return d->manager; } QModelIndex BookmarksModel::index(BookmarkNode* node) const { BookmarkNode* const parent = node->parent(); if (!parent) return QModelIndex(); return createIndex(parent->children().indexOf(node), 0, node); } void BookmarksModel::entryAdded(BookmarkNode* item) { Q_ASSERT(item && item->parent()); int row = item->parent()->children().indexOf(item); BookmarkNode* const parent = item->parent(); // item was already added so remove before beginInsertRows is called parent->remove(item); beginInsertRows(index(parent), row, row); parent->add(item, row); endInsertRows(); } void BookmarksModel::entryRemoved(BookmarkNode* parent, int row, BookmarkNode* item) { // item was already removed, re-add so beginRemoveRows works parent->add(item, row); beginRemoveRows(index(parent), row, row); parent->remove(item); endRemoveRows(); } void BookmarksModel::entryChanged(BookmarkNode* item) { QModelIndex idx = index(item); emit dataChanged(idx, idx); } bool BookmarksModel::removeRows(int row, int count, const QModelIndex& parent) { if (row < 0 || count <= 0 || (row + count) > rowCount(parent)) return false; BookmarkNode* const bookmarkNode = node(parent); for (int i = (row + count - 1) ; i >= row ; --i) { BookmarkNode* const node = bookmarkNode->children().at(i); d->manager->removeBookmark(node); } if (d->endMacro) { d->manager->undoRedoStack()->endMacro(); d->endMacro = false; } return true; } QVariant BookmarksModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case 0: return i18n("Title"); case 1: return i18n("Comment"); } } return QAbstractItemModel::headerData(section, orientation, role); } QVariant BookmarksModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.model() != this) return QVariant(); const BookmarkNode* const bookmarkNode = node(index); switch (role) { case Qt::EditRole: case Qt::DisplayRole: if (bookmarkNode->type() == BookmarkNode::Separator) { switch (index.column()) { case 0: return QString(50, 0xB7); case 1: return QString(); } } switch (index.column()) { case 0: return bookmarkNode->title; case 1: return bookmarkNode->desc; } break; case BookmarksModel::UrlRole: return QUrl(bookmarkNode->url); break; case BookmarksModel::UrlStringRole: return bookmarkNode->url; break; case BookmarksModel::DateAddedRole: return bookmarkNode->dateAdded; break; case BookmarksModel::TypeRole: return bookmarkNode->type(); break; case BookmarksModel::SeparatorRole: return (bookmarkNode->type() == BookmarkNode::Separator); break; case Qt::DecorationRole: if (index.column() == 0) { if (bookmarkNode->type() == BookmarkNode::Bookmark) { return QIcon::fromTheme(QLatin1String("globe")); } else { return QIcon::fromTheme(QLatin1String("folder")); } } } return QVariant(); } int BookmarksModel::columnCount(const QModelIndex& parent) const { return (parent.column() > 0) ? 0 : 2; } int BookmarksModel::rowCount(const QModelIndex& parent) const { if (parent.column() > 0) return 0; if (!parent.isValid()) return d->manager->bookmarks()->children().count(); const BookmarkNode* const item = static_cast(parent.internalPointer()); return item->children().count(); } QModelIndex BookmarksModel::index(int row, int column, const QModelIndex& parent) const { if (row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent)) return QModelIndex(); // get the parent node BookmarkNode* const parentNode = node(parent); return createIndex(row, column, parentNode->children().at(row)); } QModelIndex BookmarksModel::parent(const QModelIndex& index) const { if (!index.isValid()) return QModelIndex(); BookmarkNode* const itemNode = node(index); BookmarkNode* const parentNode = (itemNode ? itemNode->parent() : nullptr); if (!parentNode || parentNode == d->manager->bookmarks()) return QModelIndex(); // get the parent's row BookmarkNode* const grandParentNode = parentNode->parent(); int parentRow = grandParentNode->children().indexOf(parentNode); Q_ASSERT(parentRow >= 0); return createIndex(parentRow, 0, parentNode); } bool BookmarksModel::hasChildren(const QModelIndex& parent) const { if (!parent.isValid()) return true; const BookmarkNode* const parentNode = node(parent); return (parentNode->type() == BookmarkNode::Folder || parentNode->type() == BookmarkNode::RootFolder); } Qt::ItemFlags BookmarksModel::flags(const QModelIndex& index) const { if (!index.isValid()) return Qt::NoItemFlags; Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; BookmarkNode* const bookmarkNode = node(index); if (bookmarkNode->type() != BookmarkNode::RootFolder) flags |= Qt::ItemIsDragEnabled; if (bookmarkNode->type() != BookmarkNode::Separator && bookmarkNode->type() != BookmarkNode::RootFolder) { flags |= Qt::ItemIsEditable; } if (hasChildren(index)) flags |= Qt::ItemIsDropEnabled; return flags; } Qt::DropActions BookmarksModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList BookmarksModel::mimeTypes() const { QStringList types; types << QLatin1String("application/bookmarks.xbel"); return types; } QMimeData* BookmarksModel::mimeData(const QModelIndexList& indexes) const { QMimeData* const mimeData = new QMimeData(); QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); foreach (QModelIndex index, indexes) { if (index.column() != 0 || !index.isValid()) continue; QByteArray encodedData; QBuffer buffer(&encodedData); buffer.open(QBuffer::ReadWrite); XbelWriter writer; const BookmarkNode* const parentNode = node(index); writer.write(&buffer, parentNode); stream << encodedData; } mimeData->setData(QLatin1String("application/bookmarks.xbel"), data); return mimeData; } bool BookmarksModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { if (action == Qt::IgnoreAction) return true; if (!data->hasFormat(QLatin1String("application/bookmarks.xbel")) || column > 0) return false; QByteArray ba = data->data(QLatin1String("application/bookmarks.xbel")); QDataStream stream(&ba, QIODevice::ReadOnly); if (stream.atEnd()) return false; QUndoStack* const undoStack = d->manager->undoRedoStack(); undoStack->beginMacro(QLatin1String("Move Bookmarks")); while (!stream.atEnd()) { QByteArray encodedData; stream >> encodedData; QBuffer buffer(&encodedData); buffer.open(QBuffer::ReadOnly); XbelReader reader; BookmarkNode* const rootNode = reader.read(&buffer); QList children = rootNode->children(); for (int i = 0 ; i < children.count() ; ++i) { BookmarkNode* const bookmarkNode = children.at(i); rootNode->remove(bookmarkNode); row = qMax(0, row); BookmarkNode* const parentNode = node(parent); d->manager->addBookmark(parentNode, bookmarkNode, row); d->endMacro = true; } delete rootNode; } return true; } bool BookmarksModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid() || (flags(index) & Qt::ItemIsEditable) == 0) return false; BookmarkNode* const item = node(index); switch (role) { case Qt::EditRole: case Qt::DisplayRole: if (index.column() == 0) { d->manager->setTitle(item, value.toString()); break; } if (index.column() == 1) { d->manager->setComment(item, value.toString()); break; } return false; case BookmarksModel::UrlRole: d->manager->setUrl(item, value.toUrl().toString()); break; case BookmarksModel::UrlStringRole: d->manager->setUrl(item, value.toString()); break; default: return false; } return true; } BookmarkNode* BookmarksModel::node(const QModelIndex& index) const { BookmarkNode* const itemNode = static_cast(index.internalPointer()); if (!itemNode) return d->manager->bookmarks(); return itemNode; } // -------------------------------------------------------------- AddBookmarkProxyModel::AddBookmarkProxyModel(QObject* const parent) : QSortFilterProxyModel(parent) { } int AddBookmarkProxyModel::columnCount(const QModelIndex& parent) const { return qMin(1, QSortFilterProxyModel::columnCount(parent)); } bool AddBookmarkProxyModel::filterAcceptsRow(int srow, const QModelIndex& sparent) const { QModelIndex idx = sourceModel()->index(srow, 0, sparent); return sourceModel()->hasChildren(idx); } // -------------------------------------------------------------- TreeProxyModel::TreeProxyModel(QObject* const parent) : QSortFilterProxyModel(parent) { setFilterCaseSensitivity(Qt::CaseInsensitive); } int TreeProxyModel::columnCount(const QModelIndex&) const { // 1th column : Title // 2th column : Comment return 2; } bool TreeProxyModel::filterAcceptsRow(int srow, const QModelIndex& sparent) const { QModelIndex index = sourceModel()->index(srow, 0, sparent); if (!index.isValid()) { return false; } if (index.data().toString().contains(filterRegExp())) { return true; } for (int i = 0 ; i < sourceModel()->rowCount(index) ; ++i) { if (filterAcceptsRow(i, index)) { return true; } } return false; } void TreeProxyModel::emitResult(bool v) { emit signalFilterAccepts(v); } // -------------------------------------------------------------- class Q_DECL_HIDDEN BookmarksManager::Private { public: explicit Private() : loaded(false), bookmarkRootNode(nullptr), bookmarkModel(nullptr) { } bool loaded; BookmarkNode* bookmarkRootNode; BookmarksModel* bookmarkModel; QUndoStack commands; QString bookmarksFile; }; BookmarksManager::BookmarksManager(const QString& bookmarksFile, QObject* const parent) : QObject(parent), d(new Private) { d->bookmarksFile = bookmarksFile; load(); } BookmarksManager::~BookmarksManager() { + delete d->bookmarkRootNode; delete d; } void BookmarksManager::changeExpanded() { } void BookmarksManager::load() { if (d->loaded) return; qCDebug(DIGIKAM_GEOIFACE_LOG) << "Loading GPS bookmarks from" << d->bookmarksFile; d->loaded = true; XbelReader reader; d->bookmarkRootNode = reader.read(d->bookmarksFile); if (reader.error() != QXmlStreamReader::NoError) { QMessageBox::warning(nullptr, i18n("Loading Bookmark"), i18n("Error when loading bookmarks on line %1, column %2:\n%3", reader.lineNumber(), reader.columnNumber(), reader.errorString())); } } void BookmarksManager::save() { if (!d->loaded) return; qCDebug(DIGIKAM_GEOIFACE_LOG) << "Saving GPS bookmarks to" << d->bookmarksFile; XbelWriter writer; if (!writer.write(d->bookmarksFile, d->bookmarkRootNode)) { qCWarning(DIGIKAM_GEOIFACE_LOG) << "BookmarkManager: error saving to" << d->bookmarksFile; } } void BookmarksManager::addBookmark(BookmarkNode* const parent, BookmarkNode* const node, int row) { if (!d->loaded) return; Q_ASSERT(parent); InsertBookmarksCommand* const command = new InsertBookmarksCommand(this, parent, node, row); d->commands.push(command); } void BookmarksManager::removeBookmark(BookmarkNode* const node) { if (!d->loaded) return; Q_ASSERT(node); BookmarkNode* const parent = node->parent(); int row = parent->children().indexOf(node); RemoveBookmarksCommand* const command = new RemoveBookmarksCommand(this, parent, row); d->commands.push(command); } void BookmarksManager::setTitle(BookmarkNode* const node, const QString& newTitle) { if (!d->loaded) return; Q_ASSERT(node); ChangeBookmarkCommand* const command = new ChangeBookmarkCommand(this, node, newTitle, ChangeBookmarkCommand::Title); d->commands.push(command); } void BookmarksManager::setUrl(BookmarkNode* const node, const QString& newUrl) { if (!d->loaded) return; Q_ASSERT(node); ChangeBookmarkCommand* const command = new ChangeBookmarkCommand(this, node, newUrl, ChangeBookmarkCommand::Url); d->commands.push(command); } void BookmarksManager::setComment(BookmarkNode* const node, const QString& newDesc) { if (!d->loaded) return; Q_ASSERT(node); ChangeBookmarkCommand* const command = new ChangeBookmarkCommand(this, node, newDesc, ChangeBookmarkCommand::Desc); d->commands.push(command); } BookmarkNode* BookmarksManager::bookmarks() { if (!d->loaded) load(); return d->bookmarkRootNode; } BookmarksModel* BookmarksManager::bookmarksModel() { if (!d->bookmarkModel) d->bookmarkModel = new BookmarksModel(this, this); return d->bookmarkModel; } QUndoStack* BookmarksManager::undoRedoStack() const { return &d->commands; } void BookmarksManager::importBookmarks() { QString fileName = DFileDialog::getOpenFileName(nullptr, i18n("Open File"), QString(), i18n("XBEL (*.xbel *.xml)")); if (fileName.isEmpty()) return; XbelReader reader; BookmarkNode* const importRootNode = reader.read(fileName); if (reader.error() != QXmlStreamReader::NoError) { QMessageBox::warning(nullptr, i18n("Loading Bookmark"), i18n("Error when loading bookmarks on line %1, column %2:\n%3", reader.lineNumber(), reader.columnNumber(), reader.errorString())); } importRootNode->setType(BookmarkNode::Folder); importRootNode->title = i18n("Imported %1", QDate::currentDate().toString(Qt::SystemLocaleShortDate)); addBookmark(bookmarks(), importRootNode); } void BookmarksManager::exportBookmarks() { QString fileName = DFileDialog::getSaveFileName(nullptr, i18n("Save File"), i18n("%1 Bookmarks.xbel", QCoreApplication::applicationName()), i18n("XBEL (*.xbel *.xml)")); if (fileName.isEmpty()) return; XbelWriter writer; if (!writer.write(fileName, d->bookmarkRootNode)) QMessageBox::critical(nullptr, i18n("Export error"), i18n("error saving bookmarks")); } } // namespace Digikam