diff --git a/NEWS b/NEWS index 226b6cb5ee..cbfa0c8354 100644 --- a/NEWS +++ b/NEWS @@ -1,640 +1,641 @@ digiKam 7.0.0-beta3 - Release date: 2020-02-23 ***************************************************************************************************** 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 : Update internal libraw engine to last 201910 snapshot aka next 0.20.0 release (https://www.libraw.org/news/libraw-snapshot-201910) General : All bundles have switched to last Qt 5.14.0. Linux and MacOS use QtWebEngine instead QtWebKit. 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 Tag-Paths properties. +General : Table-view is now able to show digiKam Tag-Paths properties. +General : Table-view is now able to show Photo date taken properties. ***************************************************************************************************** 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 ==> - +621 ==> 397819 - digiKam not showing date taken. +622 ==> diff --git a/core/app/views/tableview/tableview_column_file.cpp b/core/app/views/tableview/tableview_column_file.cpp index 6640bcdaf6..c406790e18 100644 --- a/core/app/views/tableview/tableview_column_file.cpp +++ b/core/app/views/tableview/tableview_column_file.cpp @@ -1,274 +1,275 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2013-02-25 * Description : Table view column helpers: File properties * * Copyright (C) 2017-2020 by Gilles Caulier * Copyright (C) 2013 by Michael G. Hansen * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "tableview_column_file.h" // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "itempropertiestab.h" #include "iteminfo.h" namespace Digikam { namespace TableViewColumns { ColumnFileProperties::ColumnFileProperties(TableViewShared* const tableViewShared, const TableViewColumnConfiguration& pConfiguration, const SubColumn pSubColumn, QObject* const parent) : TableViewColumn(tableViewShared, pConfiguration, parent), subColumn(pSubColumn) { } TableViewColumnDescription ColumnFileProperties::getDescription() { TableViewColumnDescription description(QLatin1String("file-properties"), i18n("File properties")); description.setIcon(QLatin1String("dialog-information")); description.addSubColumn(TableViewColumnDescription(QLatin1String("filename"), i18n("Filename"))); description.addSubColumn(TableViewColumnDescription(QLatin1String("filepath"), i18n("Path"))); description.addSubColumn(TableViewColumnDescription(QLatin1String("filesize"), i18n("Size"))); description.addSubColumn(TableViewColumnDescription(QLatin1String("filelastmodified"), i18n("Last modified"))); return description; } QStringList ColumnFileProperties::getSubColumns() { QStringList columns; columns << QLatin1String("filename") << QLatin1String("filepath") << QLatin1String("filesize") << QLatin1String("filelastmodified"); return columns; } QString ColumnFileProperties::getTitle() const { switch (subColumn) { case SubColumnName: return i18n("Filename"); case SubColumnFilePath: return i18n("Path"); case SubColumnSize: return i18n("Size"); case SubColumnLastModified: return i18n("Last modified"); } return QString(); } TableViewColumn::ColumnFlags ColumnFileProperties::getColumnFlags() const { if ( (subColumn == SubColumnSize) || (subColumn == SubColumnLastModified) ) { return (ColumnCustomSorting | ColumnHasConfigurationWidget); } return ColumnNoFlags; } QVariant ColumnFileProperties::data(TableViewModel::Item* const item, const int role) const { if ( (role != Qt::DisplayRole) && (role != Qt::TextAlignmentRole) ) { return QVariant(); } if (role == Qt::TextAlignmentRole) { switch (subColumn) { case SubColumnSize: return QVariant(Qt::Alignment(Qt::AlignRight | Qt::AlignVCenter)); default: return QVariant(); } } const ItemInfo info = s->tableViewModel->infoFromItem(item); switch (subColumn) { case SubColumnName: return info.fileUrl().fileName(); break; case SubColumnFilePath: return QDir::toNativeSeparators(info.fileUrl().toLocalFile()); break; case SubColumnSize: { /// @todo Add configuration options for SI-prefixes /// @todo Use an enum instead to avoid lots of string comparisons const QString formatKey = configuration.getSetting(QLatin1String("format"), QLatin1String("human")); if (formatKey == QLatin1String("human")) { return ItemPropertiesTab::humanReadableBytesCount(info.fileSize()); } else { // formatKey == "plain" return QLocale().toString(info.fileSize()); } break; } case SubColumnLastModified: { const QDateTime lastModifiedTime = info.modDateTime(); return QLocale().toString(lastModifiedTime, QLocale::ShortFormat); } } return QVariant(); } TableViewColumn::ColumnCompareResult ColumnFileProperties::compare(TableViewModel::Item* const itemA, TableViewModel::Item* const itemB) const { const ItemInfo infoA = s->tableViewModel->infoFromItem(itemA); const ItemInfo infoB = s->tableViewModel->infoFromItem(itemB); switch (subColumn) { case SubColumnSize: { const int sizeA = infoA.fileSize(); const int sizeB = infoB.fileSize(); return compareHelper(sizeA, sizeB); } case SubColumnLastModified: { const QDateTime dtA = infoA.modDateTime(); const QDateTime dtB = infoB.modDateTime(); return compareHelper(dtA, dtB); } default: { qCWarning(DIGIKAM_GENERAL_LOG) << "file: unimplemented comparison, subColumn=" << subColumn; + return CmpEqual; } } } // --------------------------------------------------------------------------------------- ColumnFileConfigurationWidget::ColumnFileConfigurationWidget(TableViewShared* const sharedObject, const TableViewColumnConfiguration& columnConfiguration, QWidget* const parentWidget) : TableViewColumnConfigurationWidget(sharedObject, columnConfiguration, parentWidget), subColumn(ColumnFileProperties::SubColumnName), selectorSizeType(nullptr) { ColumnFileProperties::getSubColumnIndex(configuration.columnId, &subColumn); switch (subColumn) { case ColumnFileProperties::SubColumnSize: { QFormLayout* const box1 = new QFormLayout(); selectorSizeType = new QComboBox(this); selectorSizeType->addItem(i18n("Human readable"), QLatin1String("human")); selectorSizeType->addItem(i18n("Plain"), QLatin1String("plain")); box1->addRow(i18n("Display format"), selectorSizeType); setLayout(box1); const int index = selectorSizeType->findData(configuration.getSetting(QLatin1String("format"), QLatin1String("human"))); - selectorSizeType->setCurrentIndex(index>=0 ? index : 0); + selectorSizeType->setCurrentIndex((index >= 0) ? index : 0); break; } default: { break; } } } ColumnFileConfigurationWidget::~ColumnFileConfigurationWidget() { } TableViewColumnConfiguration ColumnFileConfigurationWidget::getNewConfiguration() { const QString formatKey = selectorSizeType->itemData(selectorSizeType->currentIndex()).toString(); configuration.columnSettings.insert(QLatin1String("format"), formatKey); return configuration; } void ColumnFileProperties::setConfiguration(const TableViewColumnConfiguration& newConfiguration) { configuration = newConfiguration; emit signalAllDataChanged(); } TableViewColumnConfigurationWidget* ColumnFileProperties::getConfigurationWidget(QWidget* const parentWidget) const { return new ColumnFileConfigurationWidget(s, configuration, parentWidget); } } // namespace TableViewColumns } // namespace Digikam diff --git a/core/app/views/tableview/tableview_column_photo.cpp b/core/app/views/tableview/tableview_column_photo.cpp index 3cded724cf..678fabab10 100644 --- a/core/app/views/tableview/tableview_column_photo.cpp +++ b/core/app/views/tableview/tableview_column_photo.cpp @@ -1,493 +1,524 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2013-03-14 * Description : Table view column helpers: Photo properties * * Copyright (C) 2017-2020 by Gilles Caulier * Copyright (C) 2013 by Michael G. Hansen * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "tableview_column_photo.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "coredbinfocontainers.h" #include "itempropertiestab.h" #include "dmetadata.h" #include "iteminfo.h" namespace Digikam { namespace TableViewColumns { ColumnPhotoProperties::ColumnPhotoProperties(TableViewShared* const tableViewShared, const TableViewColumnConfiguration& pConfiguration, const SubColumn pSubColumn, QObject* const parent) : TableViewColumn(tableViewShared, pConfiguration, parent), subColumn(pSubColumn) { } ColumnPhotoProperties::~ColumnPhotoProperties() { } QStringList ColumnPhotoProperties::getSubColumns() { QStringList columns; columns << QLatin1String("cameramaker") << QLatin1String("cameramodel") << QLatin1String("lens") << QLatin1String("aperture") << QLatin1String("focal") << QLatin1String("exposure") << QLatin1String("sensitivity") << QLatin1String("modeprogram") << QLatin1String("flash") - << QLatin1String("whitebalance"); + << QLatin1String("whitebalance") + << QLatin1String("date"); return columns; } TableViewColumnDescription ColumnPhotoProperties::getDescription() { TableViewColumnDescription description(QLatin1String("photo-properties"), i18n("Photo properties")); description.setIcon(QLatin1String("camera-photo")); description.addSubColumn(TableViewColumnDescription(QLatin1String("cameramaker"), i18n("Camera maker"))); description.addSubColumn(TableViewColumnDescription(QLatin1String("cameramodel"), i18n("Camera model"))); description.addSubColumn(TableViewColumnDescription(QLatin1String("lens"), i18n("Lens"))); description.addSubColumn(TableViewColumnDescription(QLatin1String("aperture"), i18n("Aperture"))); description.addSubColumn(TableViewColumnDescription(QLatin1String("focal"), i18n("Focal length"))); description.addSubColumn(TableViewColumnDescription(QLatin1String("exposure"), i18n("Exposure"))); description.addSubColumn(TableViewColumnDescription(QLatin1String("sensitivity"), i18n("Sensitivity"))); description.addSubColumn(TableViewColumnDescription(QLatin1String("modeprogram"), i18n("Mode/program"))); description.addSubColumn(TableViewColumnDescription(QLatin1String("flash"), i18n("Flash"))); description.addSubColumn(TableViewColumnDescription(QLatin1String("whitebalance"), i18n("White balance"))); + description.addSubColumn(TableViewColumnDescription(QLatin1String("date"), i18n("Date"))); return description; } QString ColumnPhotoProperties::getTitle() const { switch (subColumn) { case SubColumnCameraMaker: return i18n("Camera maker"); case SubColumnCameraModel: return i18n("Camera model"); case SubColumnLens: return i18n("Lens"); case SubColumnAperture: return i18n("Aperture"); case SubColumnFocal: return i18n("Focal length"); case SubColumnExposure: return i18n("Exposure"); case SubColumnSensitivity: return i18n("Sensitivity"); case SubColumnModeProgram: return i18n("Mode/program"); case SubColumnFlash: return i18n("Flash"); case SubColumnWhiteBalance: return i18n("White balance"); + + case SubColumnDate: + return i18n("Date"); } return QString(); } TableViewColumn::ColumnFlags ColumnPhotoProperties::getColumnFlags() const { ColumnFlags flags(ColumnNoFlags); if ( (subColumn == SubColumnAperture) || (subColumn == SubColumnFocal) || (subColumn == SubColumnExposure) || (subColumn == SubColumnSensitivity) ) { flags |= ColumnCustomSorting; } if (subColumn == SubColumnExposure) { flags |= ColumnHasConfigurationWidget; } + if (subColumn == SubColumnDate) + { + return (ColumnCustomSorting | ColumnHasConfigurationWidget); + } + return flags; } QVariant ColumnPhotoProperties::data(TableViewModel::Item* const item, const int role) const { if (role != Qt::DisplayRole) { return QVariant(); } switch (subColumn) { case SubColumnCameraMaker: { QString cameraMaker = s->tableViewModel->itemDatabaseFieldRaw(item, - DatabaseFields::Set(DatabaseFields::Make)).toString(); + DatabaseFields::Set(DatabaseFields::Make)).toString(); ItemPropertiesTab::shortenedMakeInfo(cameraMaker); + return cameraMaker; } case SubColumnCameraModel: { QString cameraModel = s->tableViewModel->itemDatabaseFieldRaw(item, - DatabaseFields::Set(DatabaseFields::Model)).toString(); + DatabaseFields::Set(DatabaseFields::Model)).toString(); ItemPropertiesTab::shortenedModelInfo(cameraModel); + return cameraModel; } case SubColumnLens: { const QString cameraLens = s->tableViewModel->itemDatabaseFieldRaw(item, - DatabaseFields::Set(DatabaseFields::Lens)).toString(); + DatabaseFields::Set(DatabaseFields::Lens)).toString(); + return cameraLens; } case SubColumnAperture: { const QVariant apertureVariant = s->tableViewModel->itemDatabaseFieldRaw(item, - DatabaseFields::Set(DatabaseFields::Aperture)); + DatabaseFields::Set(DatabaseFields::Aperture)); const QString apertureString = DMetadata::valueToString(apertureVariant, MetadataInfo::Aperture); return apertureString; } case SubColumnFocal: { /// @todo Make this configurable const DatabaseFields::Set requiredSet = DatabaseFields::Set(DatabaseFields::FocalLength | DatabaseFields::FocalLength35); const TableViewModel::DatabaseFieldsHashRaw rawFields = s->tableViewModel->itemDatabaseFieldsRaw(item, requiredSet); const QVariant focalLengthVariant = rawFields.value(DatabaseFields::FocalLength); const QString focalLengthString = DMetadata::valueToString(focalLengthVariant, MetadataInfo::FocalLength); const QVariant focalLength35Variant = rawFields.value(DatabaseFields::FocalLength35); const QString focalLength35String = DMetadata::valueToString(focalLength35Variant, MetadataInfo::FocalLengthIn35mm); if (focalLength35String.isEmpty()) { return focalLengthString; } if (focalLengthString.isEmpty()) { return QString(); } /// @todo What if only 35 mm is set? return i18n("%1 (%2)", focalLengthString, focalLength35String); } case SubColumnExposure: { /// @todo Add a configuration option for fraction vs number, units s vs ms vs mus const QVariant exposureVariant = s->tableViewModel->itemDatabaseFieldRaw(item, - DatabaseFields::Set(DatabaseFields::ExposureTime)); + DatabaseFields::Set(DatabaseFields::ExposureTime)); if (configuration.getSetting(QLatin1String("format"), QLatin1String("fraction")) == QLatin1String("fraction")) { const QString exposureString = DMetadata::valueToString(exposureVariant, MetadataInfo::ExposureTime); return exposureString; } if (!exposureVariant.isValid()) { return QString(); } const QString unitKey = configuration.getSetting(QLatin1String("unit"), QLatin1String("seconds")); double multiplier = 1.0; KLocalizedString exposureTimeLocalizedString = ki18n("%1 s"); if (unitKey == QLatin1String("milliseconds")) { multiplier = 1000.0; exposureTimeLocalizedString = ki18n("%1 ms"); } else if (unitKey == QLatin1String("microseconds")) { multiplier = 1000000.0; exposureTimeLocalizedString = ki18n("%1 µs"); } const double exposureTime = exposureVariant.toDouble() * multiplier; /// @todo Seems like we have to check for 0 here, too, as an invalid value if (exposureTime == 0) { return QString(); } /// @todo Remove trailing zeros? /// @todo Align right? --> better align at decimal point const QString exposureTimeString = exposureTimeLocalizedString.subs(QLocale().toString(exposureTime, 'g', 3)).toString(); return exposureTimeString; } case SubColumnSensitivity: { const QVariant sensitivityVariant = s->tableViewModel->itemDatabaseFieldRaw(item, DatabaseFields::Set(DatabaseFields::Sensitivity)); const QString sensitivityString = DMetadata::valueToString(sensitivityVariant, MetadataInfo::Sensitivity); if (sensitivityString.isEmpty()) { return QString(); } return i18n("%1 ISO", sensitivityString); } case SubColumnModeProgram: { const DatabaseFields::Set requiredSet = DatabaseFields::Set(DatabaseFields::ExposureMode | DatabaseFields::ExposureProgram); const TableViewModel::DatabaseFieldsHashRaw rawFields = s->tableViewModel->itemDatabaseFieldsRaw(item, requiredSet); const QVariant exposureModeVariant = rawFields.value(DatabaseFields::ExposureMode); const QString exposureModeString = DMetadata::valueToString(exposureModeVariant, MetadataInfo::ExposureMode); const QVariant exposureProgramVariant = rawFields.value(DatabaseFields::ExposureProgram); const QString exposureProgramString = DMetadata::valueToString(exposureProgramVariant, MetadataInfo::ExposureProgram); if (exposureModeString.isEmpty() && exposureProgramString.isEmpty()) { return QString(); } else if (!exposureModeString.isEmpty() && exposureProgramString.isEmpty()) { return exposureModeString; } else if (exposureModeString.isEmpty() && !exposureProgramString.isEmpty()) { return exposureProgramString; } return QString::fromUtf8("%1 / %2").arg(exposureModeString).arg(exposureProgramString); } case SubColumnFlash: { const QVariant flashModeVariant = s->tableViewModel->itemDatabaseFieldRaw(item, - DatabaseFields::Set(DatabaseFields::FlashMode)); + DatabaseFields::Set(DatabaseFields::FlashMode)); const QString flashModeString = DMetadata::valueToString(flashModeVariant, MetadataInfo::FlashMode); return flashModeString; } case SubColumnWhiteBalance: { const QVariant whiteBalanceVariant = s->tableViewModel->itemDatabaseFieldRaw(item, - DatabaseFields::Set(DatabaseFields::WhiteBalance)); + DatabaseFields::Set(DatabaseFields::WhiteBalance)); const QString whiteBalanceString = DMetadata::valueToString(whiteBalanceVariant, MetadataInfo::WhiteBalance); return whiteBalanceString; } + + case SubColumnDate: + { + const ItemInfo info = s->tableViewModel->infoFromItem(item); + const QDateTime dt = info.dateTime(); + + return QLocale().toString(dt, QLocale::ShortFormat); + } } return QVariant(); } TableViewColumn::ColumnCompareResult ColumnPhotoProperties::compare(TableViewModel::Item* const itemA, TableViewModel::Item* const itemB) const { const ItemInfo infoA = s->tableViewModel->infoFromItem(itemA); const ItemInfo infoB = s->tableViewModel->infoFromItem(itemB); switch (subColumn) { case SubColumnAperture: { const QVariant variantA = s->tableViewModel->itemDatabaseFieldRaw(itemA, - DatabaseFields::Set(DatabaseFields::Aperture)); + DatabaseFields::Set(DatabaseFields::Aperture)); const QVariant variantB = s->tableViewModel->itemDatabaseFieldRaw(itemB, - DatabaseFields::Set(DatabaseFields::Aperture)); + DatabaseFields::Set(DatabaseFields::Aperture)); const double apertureA = variantA.toDouble(); const double apertureB = variantB.toDouble(); return compareHelper(apertureA, apertureB); } case SubColumnFocal: { /// @todo This just works if both have focal length set, not if focal length 35 has to be used const QVariant variantA = s->tableViewModel->itemDatabaseFieldRaw(itemA, - DatabaseFields::Set(DatabaseFields::FocalLength)); + DatabaseFields::Set(DatabaseFields::FocalLength)); const QVariant variantB = s->tableViewModel->itemDatabaseFieldRaw(itemB, - DatabaseFields::Set(DatabaseFields::FocalLength)); + DatabaseFields::Set(DatabaseFields::FocalLength)); const double focalLengthA = variantA.toDouble(); const double focalLengthB = variantB.toDouble(); return compareHelper(focalLengthA, focalLengthB); } case SubColumnExposure: { const QVariant variantA = s->tableViewModel->itemDatabaseFieldRaw(itemA, - DatabaseFields::Set(DatabaseFields::ExposureTime)); + DatabaseFields::Set(DatabaseFields::ExposureTime)); const QVariant variantB = s->tableViewModel->itemDatabaseFieldRaw(itemB, - DatabaseFields::Set(DatabaseFields::ExposureTime)); + DatabaseFields::Set(DatabaseFields::ExposureTime)); const double exposureTimeA = variantA.toDouble(); const double exposureTimeB = variantB.toDouble(); return compareHelper(exposureTimeA, exposureTimeB); } case SubColumnSensitivity: { const QVariant variantA = s->tableViewModel->itemDatabaseFieldRaw(itemA, - DatabaseFields::Set(DatabaseFields::Sensitivity)); + DatabaseFields::Set(DatabaseFields::Sensitivity)); const QVariant variantB = s->tableViewModel->itemDatabaseFieldRaw(itemB, - DatabaseFields::Set(DatabaseFields::Sensitivity)); + DatabaseFields::Set(DatabaseFields::Sensitivity)); const double sensitivityA = variantA.toDouble(); const double sensitivityB = variantB.toDouble(); return compareHelper(sensitivityA, sensitivityB); } + case SubColumnDate: + { + const QDateTime dtA = infoA.dateTime(); + const QDateTime dtB = infoB.dateTime(); + + return compareHelper(dtA, dtB); + } + default: { qCWarning(DIGIKAM_GENERAL_LOG) << "item: unimplemented comparison, subColumn=" << subColumn; + return CmpEqual; } } } TableViewColumnConfigurationWidget* ColumnPhotoProperties::getConfigurationWidget(QWidget* const parentWidget) const { TableViewColumnConfiguration myConfiguration = getConfiguration(); - return new ColumnPhotoConfigurationWidget(s, myConfiguration, parentWidget); + + return (new ColumnPhotoConfigurationWidget(s, myConfiguration, parentWidget)); } // --------------------------------------------------------------------------------------------------------------------- ColumnPhotoConfigurationWidget::ColumnPhotoConfigurationWidget(TableViewShared* const sharedObject, const TableViewColumnConfiguration& columnConfiguration, QWidget* const parentWidget) : TableViewColumnConfigurationWidget(sharedObject, columnConfiguration, parentWidget), subColumn(ColumnPhotoProperties::SubColumnExposure), selectorExposureTimeFormat(nullptr), selectorExposureTimeUnit(nullptr) { ColumnPhotoProperties::getSubColumnIndex(configuration.columnId, &subColumn); switch (subColumn) { case ColumnPhotoProperties::SubColumnExposure: { QFormLayout* const box1 = new QFormLayout(); selectorExposureTimeFormat = new QComboBox(this); selectorExposureTimeFormat->addItem(i18n("Fraction"), QLatin1String("fraction")); selectorExposureTimeFormat->addItem(i18n("Rational"), QLatin1String("rational")); box1->addRow(i18n("Format"), selectorExposureTimeFormat); selectorExposureTimeUnit = new QComboBox(this); selectorExposureTimeUnit->addItem(i18n("Seconds"), QLatin1String("seconds")); selectorExposureTimeUnit->addItem(i18n("Milliseconds"), QLatin1String("milliseconds")); selectorExposureTimeUnit->addItem(i18n("Microseconds"), QLatin1String("microseconds")); box1->addRow(i18n("Unit"), selectorExposureTimeUnit); setLayout(box1); const int indexF = selectorExposureTimeFormat->findData(configuration.getSetting(QLatin1String("format"), QLatin1String("fraction"))); - selectorExposureTimeFormat->setCurrentIndex(indexF >= 0 ? indexF : 0); + selectorExposureTimeFormat->setCurrentIndex((indexF >= 0) ? indexF : 0); const int indexU = selectorExposureTimeUnit->findData(configuration.getSetting(QLatin1String("unit"), QLatin1String("seconds"))); - selectorExposureTimeUnit->setCurrentIndex(indexU >= 0 ? indexU : 0); + selectorExposureTimeUnit->setCurrentIndex((indexU >= 0) ? indexU : 0); slotUpdateUI(); connect(selectorExposureTimeFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateUI())); break; } default: { break; } } } ColumnPhotoConfigurationWidget::~ColumnPhotoConfigurationWidget() { } TableViewColumnConfiguration ColumnPhotoConfigurationWidget::getNewConfiguration() { const QString formatKey = selectorExposureTimeFormat->itemData(selectorExposureTimeFormat->currentIndex()).toString(); configuration.columnSettings.insert(QLatin1String("format"), formatKey); const QString unitKey = selectorExposureTimeUnit->itemData(selectorExposureTimeUnit->currentIndex()).toString(); configuration.columnSettings.insert(QLatin1String("unit"), unitKey); return configuration; } void ColumnPhotoProperties::setConfiguration(const TableViewColumnConfiguration& newConfiguration) { configuration = newConfiguration; emit signalAllDataChanged(); } void ColumnPhotoConfigurationWidget::slotUpdateUI() { if (selectorExposureTimeFormat) { const QString currentKey = selectorExposureTimeFormat->itemData(selectorExposureTimeFormat->currentIndex()).toString(); const bool needsUnits = (currentKey == QLatin1String("rational")); selectorExposureTimeUnit->setEnabled(needsUnits); } } } // namespace TableViewColumns } // namespace Digikam diff --git a/core/app/views/tableview/tableview_column_photo.h b/core/app/views/tableview/tableview_column_photo.h index 97a82dad32..d7ac5e1a10 100644 --- a/core/app/views/tableview/tableview_column_photo.h +++ b/core/app/views/tableview/tableview_column_photo.h @@ -1,116 +1,117 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2013-03-14 * Description : Table view column helpers: Photo properties * * Copyright (C) 2017-2020 by Gilles Caulier * Copyright (C) 2013 by Michael G. Hansen * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_TABLE_VIEW_COLUMN_PHOTO_H #define DIGIKAM_TABLE_VIEW_COLUMN_PHOTO_H // Qt includes #include // Local includes #include "tableview_columnfactory.h" class QComboBox; namespace Digikam { namespace TableViewColumns { class ColumnPhotoProperties : public TableViewColumn { Q_OBJECT public: enum SubColumn { SubColumnCameraMaker = 0, SubColumnCameraModel = 1, SubColumnLens = 2, SubColumnAperture = 3, SubColumnFocal = 4, SubColumnExposure = 5, SubColumnSensitivity = 6, SubColumnModeProgram = 7, SubColumnFlash = 8, - SubColumnWhiteBalance = 9 + SubColumnWhiteBalance = 9, + SubColumnDate = 10 }; private: SubColumn subColumn; public: explicit ColumnPhotoProperties(TableViewShared* const tableViewShared, const TableViewColumnConfiguration& pConfiguration, const SubColumn pSubColumn, QObject* const parent = nullptr); virtual ~ColumnPhotoProperties(); virtual QString getTitle() const; virtual ColumnFlags getColumnFlags() const; virtual QVariant data(TableViewModel::Item* const item, const int role) const; virtual ColumnCompareResult compare(TableViewModel::Item* const itemA, TableViewModel::Item* const itemB) const; virtual TableViewColumnConfigurationWidget* getConfigurationWidget(QWidget* const parentWidget) const; virtual void setConfiguration(const TableViewColumnConfiguration& newConfiguration); static TableViewColumnDescription getDescription(); static QStringList getSubColumns(); }; // ---------------------------------------------------------------------------------------------------------------------- class ColumnPhotoConfigurationWidget : public TableViewColumnConfigurationWidget { Q_OBJECT public: explicit ColumnPhotoConfigurationWidget(TableViewShared* const sharedObject, const TableViewColumnConfiguration& columnConfiguration, QWidget* const parentWidget); virtual ~ColumnPhotoConfigurationWidget(); virtual TableViewColumnConfiguration getNewConfiguration(); private Q_SLOTS: void slotUpdateUI(); private: ColumnPhotoProperties::SubColumn subColumn; QComboBox* selectorExposureTimeFormat; QComboBox* selectorExposureTimeUnit; }; } // namespace TableViewColumns } // namespace Digikam #endif // DIGIKAM_TABLE_VIEW_COLUMN_PHOTO_H diff --git a/core/app/views/tableview/tableview_columnfactory.h b/core/app/views/tableview/tableview_columnfactory.h index 1509b8ef89..bc41e9a967 100644 --- a/core/app/views/tableview/tableview_columnfactory.h +++ b/core/app/views/tableview/tableview_columnfactory.h @@ -1,338 +1,339 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2013-02-12 * Description : Table view column helpers * * Copyright (C) 2017-2020 by Gilles Caulier * Copyright (C) 2013 by Michael G. Hansen * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_TABLE_VIEW_COLUMNFACTORY_H #define DIGIKAM_TABLE_VIEW_COLUMNFACTORY_H // Qt includes #include #include #include // Local includes #include "tableview_model.h" #include "tableview_shared.h" class QModelIndex; class QStyleOptionViewItem; class KConfigGroup; namespace Digikam { class ItemInfo; class ThumbnailSize; class TableViewColumnConfiguration { public: explicit TableViewColumnConfiguration(const QString& id = QString()) : columnId(id), columnSettings() { } QString getSetting(const QString& key, const QString& defaultValue = QString()) const { if (!columnSettings.contains(key)) { return defaultValue; } return columnSettings.value(key); } void loadSettings(const KConfigGroup& configGroup); void saveSettings(KConfigGroup& configGroup) const; public: QString columnId; QHash columnSettings; }; // ---------------------------------------------------------------------------- class TableViewColumnDescription { public: explicit TableViewColumnDescription() : columnId(), columnTitle(), columnIcon(), columnSettings(), subColumns() { } explicit TableViewColumnDescription(const QString& id, const QString& title, const QString& settingKey = QString(), const QString& settingValue = QString()) : columnId(id), columnTitle(title), columnIcon(), columnSettings(), subColumns() { if (!settingKey.isEmpty()) { addSetting(settingKey, settingValue); } } public: typedef QList List; QString columnId; QString columnTitle; QString columnIcon; QHash columnSettings; QList subColumns; public: void addSubColumn(const TableViewColumnDescription& subColumnDescription) { subColumns << subColumnDescription; } void addSetting(const QString& key, const QString& value) { columnSettings.insert(key, value); } TableViewColumnConfiguration toConfiguration() const { TableViewColumnConfiguration configuration; configuration.columnId = columnId; configuration.columnSettings = columnSettings; return configuration; } TableViewColumnDescription setIcon(const QString& iconName) { columnIcon = iconName; return *this; } static bool FindInListById(const TableViewColumnDescription::List& listToSearch, const QString& targetId, TableViewColumnDescription* const resultDescription) { TableViewColumnDescription::List leftToSearch = listToSearch; while (!leftToSearch.isEmpty()) { const TableViewColumnDescription desc = leftToSearch.takeFirst(); if (desc.columnId == targetId) { *resultDescription = desc; + return true; } if (!desc.subColumns.isEmpty()) { leftToSearch << desc.subColumns; } } return false; } }; // ---------------------------------------------------------------------------- class TableViewColumnConfigurationWidget : public QWidget { Q_OBJECT public: explicit TableViewColumnConfigurationWidget(TableViewShared* const sharedObject, const TableViewColumnConfiguration& currentConfiguration, QWidget* const parent = nullptr); virtual ~TableViewColumnConfigurationWidget(); virtual TableViewColumnConfiguration getNewConfiguration() = 0; public: TableViewShared* const s; TableViewColumnConfiguration configuration; }; // ---------------------------------------------------------------------------- class TableViewColumn : public QObject { Q_OBJECT protected: TableViewShared* const s; TableViewColumnConfiguration configuration; public: enum ColumnFlag { ColumnNoFlags = 0, ColumnCustomPainting = 1, ColumnCustomSorting = 2, ColumnHasConfigurationWidget = 4 }; Q_DECLARE_FLAGS(ColumnFlags, ColumnFlag) enum ColumnCompareResult { CmpEqual = 0, CmpABiggerB = 1, CmpALessB = 2 }; public: explicit TableViewColumn(TableViewShared* const tableViewShared, const TableViewColumnConfiguration& pConfiguration, QObject* const parent = nullptr); virtual ~TableViewColumn(); virtual TableViewColumnConfiguration getConfiguration() const; virtual void setConfiguration(const TableViewColumnConfiguration& newConfiguration); virtual TableViewColumnConfigurationWidget* getConfigurationWidget(QWidget* const parentWidget) const; virtual ColumnFlags getColumnFlags() const; virtual QString getTitle() const = 0; virtual QVariant data(TableViewModel::Item* const item, const int role) const; virtual ColumnCompareResult compare(TableViewModel::Item* const itemA, TableViewModel::Item* const itemB) const; virtual bool columnAffectedByChangeset(const ImageChangeset& imageChangeset) const; virtual bool paint(QPainter* const painter, const QStyleOptionViewItem& option, TableViewModel::Item* const item) const; virtual QSize sizeHint(const QStyleOptionViewItem& option, TableViewModel::Item* const item) const; virtual void updateThumbnailSize(); static TableViewColumnDescription getDescription(); static bool compareHelperBoolFailCheck(const bool okA, const bool okB, ColumnCompareResult* const result); template static ColumnCompareResult compareHelper(const MyType& A, const MyType& B) { if (A == B) { return CmpEqual; } else if (A > B) { return CmpABiggerB; } return CmpALessB; } template static bool getSubColumnIndex(const QString& subColumnId, typename columnClass::SubColumn* const subColumn) { const int index = columnClass::getSubColumns().indexOf(subColumnId); if (index < 0) { return false; } *subColumn = typename columnClass::SubColumn(index); return true; } template static bool CreateFromConfiguration(TableViewShared* const tableViewShared, const TableViewColumnConfiguration& pConfiguration, TableViewColumn** const pNewColumn, QObject* const parent) { typename columnClass::SubColumn subColumn; if (!getSubColumnIndex(pConfiguration.columnId, &subColumn)) { return false; } *pNewColumn = new columnClass(tableViewShared, pConfiguration, subColumn, parent); return true; } Q_SIGNALS: void signalDataChanged(const qlonglong imageId); void signalAllDataChanged(); }; // ---------------------------------------------------------------------------- class TableViewColumnFactory : public QObject { Q_OBJECT public: explicit TableViewColumnFactory(TableViewShared* const tableViewShared, QObject* const parent = nullptr); static QList getColumnDescriptionList(); TableViewColumn* getColumn(const TableViewColumnConfiguration& columnConfiguration); private: TableViewShared* const s; }; // ---------------------------------------------------------------------------- class TableViewColumnProfile { public: TableViewColumnProfile(); ~TableViewColumnProfile(); void loadSettings(const KConfigGroup& configGroup); void saveSettings(KConfigGroup& configGroup); public: QList columnConfigurationList; QString name; QByteArray headerState; }; } // namespace Digikam Q_DECLARE_METATYPE(Digikam::TableViewColumnDescription) Q_DECLARE_OPERATORS_FOR_FLAGS(Digikam::TableViewColumn::ColumnFlags) #endif // DIGIKAM_TABLE_VIEW_COLUMNFACTORY_H diff --git a/core/app/views/tableview/tableview_model.cpp b/core/app/views/tableview/tableview_model.cpp index fa97827e59..5fb50be591 100644 --- a/core/app/views/tableview/tableview_model.cpp +++ b/core/app/views/tableview/tableview_model.cpp @@ -1,1709 +1,1709 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2013-02-12 * Description : Wrapper model for table view * * Copyright (C) 2017-2020 by Gilles Caulier * Copyright (C) 2013 by Michael G. Hansen * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "tableview_model.h" // C++ includes #include #include // Qt includes #include // Local includes #include "digikam_debug.h" #include "coredb.h" #include "coredbaccess.h" #include "coredbchangesets.h" #include "coredbfields.h" #include "coredbwatch.h" #include "itemfiltermodel.h" #include "itemfiltersettings.h" #include "applicationsettings.h" #include "iteminfo.h" #include "tableview_columnfactory.h" #include "tableview_selection_model_syncer.h" #ifdef DIGIKAM_ENABLE_MODELTEST # include "../../../tests/modeltest/modeltest.h" #endif // DIGIKAM_ENABLE_MODELTEST -#define ASSERT_MODEL(index, modelPointer) if (index.isValid()) { Q_ASSERT(index.model()==modelPointer); } +#define ASSERT_MODEL(index, modelPointer) if (index.isValid()) { Q_ASSERT(index.model() == modelPointer); } namespace Digikam { TableViewModel::Item::Item() : imageId(0), parent(nullptr), children() { } TableViewModel::Item::~Item() { qDeleteAll(children); } void TableViewModel::Item::addChild(TableViewModel::Item* const newChild) { newChild->parent = this; children << newChild; } void TableViewModel::Item::insertChild(const int pos, TableViewModel::Item* const newChild) { newChild->parent = this; children.insert(pos, newChild); } void TableViewModel::Item::takeChild(TableViewModel::Item* const oldChild) { children.removeOne(oldChild); } TableViewModel::Item* TableViewModel::Item::findChildWithImageId(const qlonglong searchImageId) { if (imageId == searchImageId) { return this; } foreach (Item* const item, children) { Item* const iItem = item->findChildWithImageId(searchImageId); if (iItem) { return iItem; } } return nullptr; } // ---------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN TableViewModel::Private { public: explicit Private() : columnObjects(), rootItem(nullptr), imageFilterSettings(), sortColumn(0), sortOrder(Qt::AscendingOrder), sortRequired(false), groupingMode(GroupingShowSubItems), cachedItemInfos(), outdated(true) { } QList columnObjects; TableViewModel::Item* rootItem; ItemFilterSettings imageFilterSettings; int sortColumn; Qt::SortOrder sortOrder; bool sortRequired; GroupingMode groupingMode; QHash cachedItemInfos; bool outdated; }; TableViewModel::TableViewModel(TableViewShared* const sharedObject, QObject* const parent) : QAbstractItemModel(parent), s(sharedObject), d(new Private()) { d->rootItem = new Item(); d->imageFilterSettings = s->imageFilterModel->imageFilterSettings(); connect(s->imageModel, SIGNAL(modelAboutToBeReset()), this, SLOT(slotSourceModelAboutToBeReset())); connect(s->imageModel, SIGNAL(modelReset()), this, SLOT(slotSourceModelReset())); connect(s->imageModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(slotSourceRowsAboutToBeInserted(QModelIndex,int,int))); connect(s->imageModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotSourceRowsInserted(QModelIndex,int,int))); connect(s->imageModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(slotSourceRowsAboutToBeRemoved(QModelIndex,int,int))); connect(s->imageModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(slotSourceRowsRemoved(QModelIndex,int,int))); connect(s->imageModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(slotSourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); connect(s->imageModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(slotSourceRowsMoved(QModelIndex,int,int,QModelIndex,int))); connect(s->imageModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(slotSourceLayoutAboutToBeChanged())); connect(s->imageModel, SIGNAL(layoutChanged()), this, SLOT(slotSourceLayoutChanged())); connect(s->imageFilterModel, SIGNAL(filterSettingsChanged(ItemFilterSettings)), this, SLOT(slotFilterSettingsChanged(ItemFilterSettings))); // We do not connect to ItemFilterModel::dataChanged, because we monitor changes directly from the database. connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)), this, SLOT(slotDatabaseImageChanged(ImageChangeset)), Qt::QueuedConnection); #ifdef DIGIKAM_ENABLE_MODELTEST new ModelTest(this, this); #endif // DIGIKAM_ENABLE_MODELTEST // We only have to trigger population of the model if data is in the source model, // otherwise the source model will tell us about any new data. const int itemsInItemModel = s->imageModel->rowCount(); if (itemsInItemModel > 0) { // populate the model once later, not now QTimer::singleShot(0, this, SLOT(slotPopulateModelWithNotifications())); } } TableViewModel::~TableViewModel() { delete d->rootItem; } int TableViewModel::columnCount(const QModelIndex& i) const { if (i.column() > 0) { return 0; } return d->columnObjects.count(); } QModelIndex TableViewModel::toItemFilterModelIndex(const QModelIndex& i) const { Item* const item = itemFromIndex(i); if (!item) { return QModelIndex(); } return s->imageFilterModel->indexForImageId(item->imageId); } QModelIndex TableViewModel::toItemModelIndex(const QModelIndex& i) const { Item* const item = itemFromIndex(i); if (!item) { return QModelIndex(); } return s->imageModel->indexForImageId(item->imageId); } QVariant TableViewModel::data(const QModelIndex& i, int role) const { Item* const item = itemFromIndex(i); if (!item) { return QVariant(); } const int columnNumber = i.column(); TableViewColumn* const myColumn = d->columnObjects.at(columnNumber); return myColumn->data(item, role); } QModelIndex TableViewModel::index(int row, int column, const QModelIndex& parent) const { Item* parentItem = d->rootItem; if (parent.isValid()) { if (parent.column() > 0) { // only column 0 can have children, the other columns can not return QModelIndex(); } parentItem = itemFromIndex(parent); } // test for valid row/column values if ( (row < 0) || (column < 0) || (column >= d->columnObjects.count()) || (row >= parentItem->children.count()) ) { return QModelIndex(); } Item* const itemPointer = parentItem->children.at(row); return createIndex(row, column, itemPointer); } QModelIndex TableViewModel::parent(const QModelIndex& childIndex) const { if (!childIndex.isValid()) { return QModelIndex(); } Item* const childItem = itemFromIndex(childIndex); Item* const parentItem = childItem->parent; if (parentItem == d->rootItem) { return QModelIndex(); } Item* const grandParentItem = parentItem->parent; const int rowIndex = grandParentItem->children.indexOf(parentItem); /// @todo What should be the column number? return createIndex(rowIndex, 0, parentItem); } int TableViewModel::rowCount(const QModelIndex& parent) const { if (parent.column() > 0) { return 0; } Item* parentItem = d->rootItem; if (parent.isValid()) { parentItem = itemFromIndex(parent); } return parentItem->children.count(); } QVariant TableViewModel::headerData(int section, Qt::Orientation orientation, int role) const { // test for valid input ranges if ((section < 0) || (section >= d->columnObjects.count())) { return QVariant(); } if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole)) { return QVariant(); } TableViewColumn* const myColumn = d->columnObjects.at(section); return myColumn->getTitle(); } void TableViewModel::addColumnAt(const TableViewColumnDescription& description, const int targetColumn) { /// @todo take additional configuration data of the column into account TableViewColumnConfiguration newConfiguration = description.toConfiguration(); addColumnAt(newConfiguration, targetColumn); } void TableViewModel::addColumnAt(const TableViewColumnConfiguration& configuration, const int targetColumn) { TableViewColumn* const newColumn = s->columnFactory->getColumn(configuration); if (!newColumn) { return; } int newColumnIndex = targetColumn; if (targetColumn < 0) { // a negative column index means "append after last column" newColumnIndex = d->columnObjects.count(); } beginInsertColumns(QModelIndex(), newColumnIndex, newColumnIndex); if (newColumnIndex >= d->columnObjects.count()) { d->columnObjects.append(newColumn); } else { d->columnObjects.insert(newColumnIndex, newColumn); } endInsertColumns(); connect(newColumn, SIGNAL(signalDataChanged(qlonglong)), this, SLOT(slotColumnDataChanged(qlonglong))); connect(newColumn, SIGNAL(signalAllDataChanged()), this, SLOT(slotColumnAllDataChanged())); } void TableViewModel::slotColumnDataChanged(const qlonglong imageId) { TableViewColumn* const senderColumn = qobject_cast(sender()); /// @todo find a faster way to find the column number const int iColumn = d->columnObjects.indexOf(senderColumn); if (iColumn < 0) { return; } const QModelIndex changedIndex = indexFromImageId(imageId, iColumn); emit dataChanged(changedIndex, changedIndex); } void TableViewModel::slotColumnAllDataChanged() { TableViewColumn* const senderColumn = qobject_cast(sender()); /// @todo find a faster way to find the column number const int iColumn = d->columnObjects.indexOf(senderColumn); if (iColumn < 0) { return; } const QModelIndex changedIndexTopLeft = index(0, iColumn, QModelIndex()); const QModelIndex changedIndexBottomRight = index(rowCount(QModelIndex())-1, iColumn, QModelIndex()); emit dataChanged(changedIndexTopLeft, changedIndexBottomRight); } void TableViewModel::removeColumnAt(const int columnIndex) { beginRemoveColumns(QModelIndex(), columnIndex, columnIndex); TableViewColumn* const removedColumn = d->columnObjects.takeAt(columnIndex); endRemoveColumns(); delete removedColumn; } TableViewColumn* TableViewModel::getColumnObject(const int columnIndex) { return d->columnObjects.at(columnIndex); } TableViewColumnProfile TableViewModel::getColumnProfile() const { TableViewColumnProfile profile; for (int i = 0 ; i < d->columnObjects.count() ; ++i) { TableViewColumnConfiguration ic = d->columnObjects.at(i)->getConfiguration(); profile.columnConfigurationList << ic; } return profile; } void TableViewModel::loadColumnProfile(const TableViewColumnProfile& columnProfile) { while (!d->columnObjects.isEmpty()) { removeColumnAt(0); } /// @todo disable updating of the model while this happens for (int i = 0 ; i < columnProfile.columnConfigurationList.count() ; ++i) { addColumnAt(columnProfile.columnConfigurationList.at(i), -1); } } void TableViewModel::slotSourceModelAboutToBeReset() { if (!s->isActive) { slotClearModel(true); return; } // the source model is about to be reset. Propagate that change: beginResetModel(); } void TableViewModel::slotSourceModelReset() { if (!s->isActive) { return; } // the source model is done resetting. slotPopulateModel(false); endResetModel(); } void TableViewModel::slotSourceRowsAboutToBeInserted(const QModelIndex& parent, int start, int end) { Q_UNUSED(parent) Q_UNUSED(start) Q_UNUSED(end) } void TableViewModel::slotSourceRowsInserted(const QModelIndex& parent, int start, int end) { if (!s->isActive) { slotClearModel(true); return; } for (int i = start ; i <= end ; ++i) { const QModelIndex sourceIndex = s->imageModel->index(i, 0, parent); addSourceModelIndex(sourceIndex, true); } } void TableViewModel::slotSourceRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { if (!s->isActive) { slotClearModel(true); return; } for (int i = start ; i <= end ; ++i) { const QModelIndex imageModelIndex = s->imageModel->index(i, 0, parent); const qlonglong imageId = s->imageModel->imageId(imageModelIndex); d->cachedItemInfos.remove(imageId); const QModelIndex tableViewIndex = indexFromImageId(imageId, 0); if (!tableViewIndex.isValid()) { continue; } Item* const item = itemFromIndex(tableViewIndex); if (!item) { continue; } beginRemoveRows(tableViewIndex.parent(), tableViewIndex.row(), tableViewIndex.row()); item->parent->takeChild(item); // delete image info of children from cache QList itemsToRemoveFromCache = item->children; while (!itemsToRemoveFromCache.isEmpty()) { Item* const itemToRemove = itemsToRemoveFromCache.takeFirst(); itemsToRemoveFromCache << itemToRemove->children; d->cachedItemInfos.remove(itemToRemove->imageId); // child items will be deleted when item is deleted } delete item; endRemoveRows(); } } void TableViewModel::slotSourceRowsRemoved(const QModelIndex& parent, int start, int end) { /// @todo Do we need to do anything here? Q_UNUSED(parent) Q_UNUSED(start) Q_UNUSED(end) } void TableViewModel::slotSourceRowsAboutToBeMoved(const QModelIndex& sourceParent, int sourceStart, int sourceEnd, const QModelIndex& destinationParent, int destinationRow) { /* beginMoveRows(sourceParent, sourceStart, sourceEnd, destinationParent, destinationRow); */ /// @todo For our items, moving stuff around does not matter --> remove this slot Q_UNUSED(sourceParent) Q_UNUSED(sourceStart) Q_UNUSED(sourceEnd) Q_UNUSED(destinationParent) Q_UNUSED(destinationRow) } void TableViewModel::slotSourceRowsMoved(const QModelIndex& sourceParent, int sourceStart, int sourceEnd, const QModelIndex& destinationParent, int destinationRow) { /// @todo For our items, moving stuff around does not matter --> remove this slot Q_UNUSED(sourceParent) Q_UNUSED(sourceStart) Q_UNUSED(sourceEnd) Q_UNUSED(destinationParent) Q_UNUSED(destinationRow) /* endMoveRows(); */ } void TableViewModel::slotSourceLayoutAboutToBeChanged() { if (!s->isActive) { slotClearModel(true); return; } /// @todo Emitting layoutAboutToBeChanged and layoutChanged is tricky, /// because we do not know what will change. /// It looks like ItemFilterModel emits layoutAboutToBeChanged and layoutChanged /// even when the resulting dataset will be empty, and ModelTest does not like that. /// For now, the easiest workaround is resetting the model /* emit layoutAboutToBeChanged(); */ beginResetModel(); } void TableViewModel::slotSourceLayoutChanged() { if (!s->isActive) { return; } /// @todo See note in TableViewModel#slotSourceLayoutAboutToBeChanged slotPopulateModel(false); endResetModel(); } void TableViewModel::slotDatabaseImageChanged(const ImageChangeset& imageChangeset) { if (!s->isActive) { slotClearModel(true); return; } /* const DatabaseFields::Set changes = imageChangeset.changes(); */ /// @todo Decide which changes are relevant here or /// let the TableViewColumn object decide which are relevant /// @todo Re-population of the model is also triggered, thus making this /// check irrelevant. Needs to be fixed. /// @todo If the user has never set which column should define the sorting, /// the sortColumn is invalid. We should set a useful default. bool needToResort = false; if ((d->sortColumn >= 0) && (d->sortColumn < d->columnObjects.count())) { TableViewColumn* const sortColumnObject = d->columnObjects.at(d->sortColumn); needToResort = sortColumnObject->columnAffectedByChangeset(imageChangeset); } foreach (const qlonglong& id, imageChangeset.ids()) { // first clear the item's cached values /// @todo Clear only the fields which were changed Item* const item = itemFromImageId(id); if (!item) { // Item is not in this model. If it is in the ItemModel, // it has been filtered out and we have to re-check the filtering. const QModelIndex& imageModelIndex = s->imageModel->indexForImageId(id); if (!imageModelIndex.isValid()) { continue; } const ItemInfo imageInfo = s->imageModel->imageInfo(imageModelIndex); if (d->imageFilterSettings.matches(imageInfo)) { // need to add the item addSourceModelIndex(imageModelIndex, true); } continue; } // remove cached info and re-insert it if (d->cachedItemInfos.contains(item->imageId)) { const ItemInfo itemInfo(item->imageId); d->cachedItemInfos.remove(item->imageId); d->cachedItemInfos.insert(item->imageId, itemInfo); } // Re-check filtering for this item. const QModelIndex changedIndexTopLeft = indexFromImageId(id, 0); if (!changedIndexTopLeft.isValid()) { continue; } const ItemInfo myItemInfo = imageInfo(changedIndexTopLeft); if (!d->imageFilterSettings.matches(myItemInfo)) { // Filter does not match, remove the item. beginRemoveRows(changedIndexTopLeft.parent(), changedIndexTopLeft.row(), changedIndexTopLeft.row()); item->parent->takeChild(item); delete item; endRemoveRows(); continue; } /// @todo Re-check grouping for this item // only update now if we do not resort later anyway if (!needToResort) { const QModelIndex changedIndexBottomRight = index(changedIndexTopLeft.row(), columnCount(changedIndexTopLeft.parent())-1, changedIndexTopLeft.parent()); if (changedIndexBottomRight.isValid()) { emit dataChanged(changedIndexTopLeft, changedIndexBottomRight); } } } if (needToResort) { scheduleResort(); } } Qt::ItemFlags TableViewModel::flags(const QModelIndex& index) const { const Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); /// @todo Handle read-only files etc. which can not be moved if (index.isValid()) { return (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags); } return (Qt::ItemIsDropEnabled | defaultFlags); } QList TableViewModel::getColumnObjects() { return d->columnObjects; } void TableViewModel::slotPopulateModelWithNotifications() { slotPopulateModel(true); } void TableViewModel::slotClearModel(const bool sendNotifications) { if (d->outdated) { return; } d->outdated = true; if (sendNotifications) { beginResetModel(); } if (d->rootItem) { delete d->rootItem; } d->rootItem = new Item(); d->cachedItemInfos.clear(); if (sendNotifications) { endResetModel(); } } void TableViewModel::slotPopulateModel(const bool sendNotifications) { if (!s->isActive) { slotClearModel(sendNotifications); return; } if (sendNotifications) { beginResetModel(); } if (d->rootItem) { delete d->rootItem; } d->rootItem = new Item(); d->cachedItemInfos.clear(); d->outdated = false; d->sortRequired = false; const int sourceRowCount = s->imageModel->rowCount(QModelIndex()); for (int i = 0 ; i < sourceRowCount ; ++i) { const QModelIndex sourceModelIndex = s->imageModel->index(i, 0); // do not send notifications in addSourceModelIndex, because this function here // already started a model reset addSourceModelIndex(sourceModelIndex, false); } if (sendNotifications) { endResetModel(); } } TableViewModel::Item* TableViewModel::createItemFromSourceIndex(const QModelIndex& imageModelIndex) { ASSERT_MODEL(imageModelIndex, s->imageModel); Item* const item = new Item(); item->imageId = s->imageModel->imageId(imageModelIndex); return item; } void TableViewModel::addSourceModelIndex(const QModelIndex& imageModelIndex, const bool sendNotifications) { ASSERT_MODEL(imageModelIndex, s->imageModel); const ItemInfo imageInfo = s->imageModel->imageInfo(imageModelIndex); const bool passedFilter = d->imageFilterSettings.matches(imageInfo); const bool allGroupsOpen = ApplicationSettings::instance()->getAllGroupsOpen(); if (!passedFilter) { return; } /// @todo Implement Grouping and sorting Item* const parentItem = d->rootItem; if (!allGroupsOpen && imageInfo.isGrouped()) { switch (d->groupingMode) { case GroupingHideGrouped: // we do not show grouped items at all return; case GroupingIgnoreGrouping: // nothing to do, we just add it to the root item break; case GroupingShowSubItems: // we do not add this subitem, because it has been automatically added to the group leader return; } } Item* const item = createItemFromSourceIndex(imageModelIndex); // Normally we do the sorting of items here on insertion. // However, if the sorting is currently outdated, we just // append the items because the model will be resorted later. int newRowIndex = parentItem->children.count(); if (!d->sortRequired) { newRowIndex = findChildSortedPosition(parentItem, item); } if (sendNotifications) { const QModelIndex parentIndex = itemIndex(parentItem); beginInsertRows(parentIndex, newRowIndex, newRowIndex); } parentItem->insertChild(newRowIndex, item); if (sendNotifications) { endInsertRows(); } if (!allGroupsOpen && (d->groupingMode == GroupingShowSubItems) && imageInfo.hasGroupedImages()) { // the item was a group leader, add its subitems const QList groupedImages = imageInfo.groupedImages(); if (sendNotifications) { const QModelIndex groupLeaderIndex = itemIndex(item); beginInsertRows(groupLeaderIndex, 0, groupedImages.count()-1); } foreach (const ItemInfo& groupedInfo, groupedImages) { d->cachedItemInfos.insert(groupedInfo.id(), groupedInfo); /// @todo Grouped items are currently not filtered. Should they? Item* const groupedItem = new Item(); groupedItem->imageId = groupedInfo.id(); // Normally we do the sorting of items here on insertion. // However, if the sorting is currently outdated, we just // append the items because the model will be resorted later. int index = item->children.count(); if (!d->sortRequired) { index = findChildSortedPosition(item, groupedItem); } item->insertChild(index, groupedItem); } if (sendNotifications) { endInsertRows(); } } } TableViewModel::Item* TableViewModel::itemFromImageId(const qlonglong imageId) const { return d->rootItem->findChildWithImageId(imageId); } TableViewModel::Item* TableViewModel::itemFromIndex(const QModelIndex& i) const { if (!i.isValid()) { return nullptr; } Q_ASSERT(i.model() == this); Item* const item = static_cast(i.internalPointer()); return item; } QModelIndex TableViewModel::fromItemFilterModelIndex(const QModelIndex& imageFilterModelIndex) { ASSERT_MODEL(imageFilterModelIndex, s->imageFilterModel); const qlonglong imageId = s->imageFilterModel->imageId(imageFilterModelIndex); if (!imageId) { return QModelIndex(); } return indexFromImageId(imageId, 0); } QModelIndex TableViewModel::fromItemModelIndex(const QModelIndex& imageModelIndex) { ASSERT_MODEL(imageModelIndex, s->imageModel); const qlonglong imageId = s->imageModel->imageId(imageModelIndex); if (!imageId) { return QModelIndex(); } return indexFromImageId(imageId, 0); } ItemInfo TableViewModel::infoFromItem(TableViewModel::Item* const item) const { /// @todo Is there a way to do it without first looking up the index in the ItemModel? const QModelIndex imageModelIndex = s->imageModel->indexForImageId(item->imageId); if (!imageModelIndex.isValid()) { const ItemInfo fromCache = d->cachedItemInfos.value(item->imageId); return fromCache; } const ItemInfo info = s->imageModel->imageInfo(imageModelIndex); return info; } ItemInfoList TableViewModel::infosFromItems(QList const items) const { ItemInfoList infos; foreach (TableViewModel::Item* const item, items) { infos << infoFromItem(item); } return infos; } TableViewModel::DatabaseFieldsHashRaw TableViewModel::itemDatabaseFieldsRaw(TableViewModel::Item* const item, const DatabaseFields::Set requestedSet) { const ItemInfo itemItemInfo = infoFromItem(item); return itemItemInfo.getDatabaseFieldsRaw(requestedSet); } QVariant TableViewModel::itemDatabaseFieldRaw(TableViewModel::Item* const item, const DatabaseFields::Set requestedField) { const TableViewModel::DatabaseFieldsHashRaw rawHash = itemDatabaseFieldsRaw(item, requestedField); if (requestedField.hasFieldsFromImageMetadata()) { const DatabaseFields::ImageMetadata requestedFieldFlag = requestedField; const QVariant value = rawHash.value(requestedFieldFlag); return value; } if (requestedField.hasFieldsFromVideoMetadata()) { const DatabaseFields::VideoMetadata requestedFieldFlag = requestedField; const QVariant value = rawHash.value(requestedFieldFlag); return value; } return QVariant(); } QModelIndex TableViewModel::indexFromImageId(const qlonglong imageId, const int columnIndex) const { Item* const item = itemFromImageId(imageId); if (!item) { return QModelIndex(); } Item* const parentItem = item->parent; /// @todo This is a waste of time because itemFromImageId already did this search. /// We should modify it to also give the row index. const int rowIndex = parentItem->children.indexOf(item); return createIndex(rowIndex, columnIndex, item); } QList TableViewModel::imageIds(const QModelIndexList& indexList) const { QList idList; foreach (const QModelIndex& index, indexList) { ASSERT_MODEL(index, this); if (index.column() > 0) { continue; } const Item* const item = itemFromIndex(index); if (!item) { continue; } idList << item->imageId; } return idList; } QList TableViewModel::imageInfos(const QModelIndexList& indexList) const { QList infoList; foreach (const QModelIndex& index, indexList) { ASSERT_MODEL(index, this); if (index.column() > 0) { continue; } Item* const item = itemFromIndex(index); if (!item) { continue; } infoList << infoFromItem(item); } return infoList; } ItemInfo TableViewModel::imageInfo(const QModelIndex& index) const { ASSERT_MODEL(index, this); Item* const item = itemFromIndex(index); if (!item) { return ItemInfo(); } return infoFromItem(item); } void TableViewModel::slotFilterSettingsChanged(const ItemFilterSettings& settings) { d->imageFilterSettings = settings; slotPopulateModel(true); } class Q_DECL_HIDDEN TableViewModel::LessThan { public: explicit LessThan(TableViewModel* const model) : m(model) { } bool operator()(const TableViewModel::Item* const itemA, const TableViewModel::Item* const itemB) { const bool compareResult = m->lessThan(const_cast(itemA), const_cast(itemB)); if (m->d->sortOrder == Qt::DescendingOrder) { return !compareResult; } return compareResult; } public: TableViewModel* m; }; QList TableViewModel::sortItems(const QList itemList) { QList sortedList = itemList; std::sort(sortedList.begin(), sortedList.end(), LessThan(this)); return sortedList; } void TableViewModel::sort(int column, Qt::SortOrder order) { d->sortColumn = column; d->sortOrder = order; /// @todo re-sort items QList itemsRequiringSorting; itemsRequiringSorting << d->rootItem; beginResetModel(); while (!itemsRequiringSorting.isEmpty()) { Item* const itemToSort = itemsRequiringSorting.takeFirst(); foreach (Item* const itemToCheck, itemToSort->children) { if (!itemToCheck->children.isEmpty()) { itemsRequiringSorting << itemToCheck; } } itemToSort->children = sortItems(itemToSort->children); } endResetModel(); } bool TableViewModel::lessThan(TableViewModel::Item* const itemA, TableViewModel::Item* const itemB) { if ((d->sortColumn < 0) || (d->sortColumn >= d->columnObjects.count())) { return (itemA->imageId < itemB->imageId); } const TableViewColumn* columnObject = s->tableViewModel->getColumnObject(d->sortColumn); if (!columnObject->getColumnFlags().testFlag(TableViewColumn::ColumnCustomSorting)) { const QString stringA = columnObject->data(itemA, Qt::DisplayRole).toString(); const QString stringB = columnObject->data(itemB, Qt::DisplayRole).toString(); if ((stringA == stringB) || (stringA.isEmpty() && stringB.isEmpty())) { - return itemA->imageId < itemB->imageId; + return (itemA->imageId < itemB->imageId); } return (stringA < stringB); } TableViewColumn::ColumnCompareResult cmpResult = columnObject->compare(itemA, itemB); if (cmpResult == TableViewColumn::CmpEqual) { // compared items are equal, use the image id to enforce a repeatable sorting const qlonglong imageIdA = itemA->imageId; const qlonglong imageIdB = itemB->imageId; return (imageIdA < imageIdB); } return (cmpResult == TableViewColumn::CmpALessB); } QMimeData* TableViewModel::mimeData(const QModelIndexList& indexes) const { // we pack the mime data via ItemModel's drag-drop handler AbstractItemDragDropHandler* const ddHandler = s->imageModel->dragDropHandler(); QModelIndexList imageModelIndexList; foreach (const QModelIndex& i, indexes) { if (i.column() > 0) { continue; } const QModelIndex imageModelIndex = toItemModelIndex(i); if (imageModelIndex.isValid()) { imageModelIndexList << imageModelIndex; } } QMimeData* const imageModelMimeData = ddHandler->createMimeData(imageModelIndexList); return imageModelMimeData; } Qt::DropActions TableViewModel::supportedDropActions() const { return (Qt::CopyAction | Qt::MoveAction); } QStringList TableViewModel::mimeTypes() const { AbstractItemDragDropHandler* const ddHandler = s->imageModel->dragDropHandler(); if (ddHandler) { return ddHandler->mimeTypes(); } return QStringList(); } bool TableViewModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { Q_UNUSED(data) Q_UNUSED(action) Q_UNUSED(row) Q_UNUSED(column) Q_UNUSED(parent) // the drop is handled by the drag-drop handler, therefore we return false here return false; } void TableViewModel::slotResortModel() { if (!d->sortRequired) { return; } beginResetModel(); sort(d->sortColumn, d->sortOrder); endResetModel(); d->sortRequired = false; } void TableViewModel::scheduleResort() { if (d->sortRequired) { return; } d->sortRequired = true; QTimer::singleShot(100, this, SLOT(slotResortModel())); } QModelIndex TableViewModel::itemIndex(TableViewModel::Item* const item) const { if ((!item) || (item==d->rootItem)) { return QModelIndex(); } const int rowIndex = item->parent->children.indexOf(item); return createIndex(rowIndex, 0, item); } bool TableViewModel::hasChildren(const QModelIndex& parent) const { Item* parentItem = d->rootItem; if (parent.isValid()) { if (parent.column() > 0) { // only column 0 can have children return false; } parentItem = itemFromIndex(parent); } return !parentItem->children.isEmpty(); } qlonglong TableViewModel::imageId(const QModelIndex& anIndex) const { const Item* const anItem = itemFromIndex(anIndex); if (!anItem) { - return -1; + return (-1); } return anItem->imageId; } QList TableViewModel::allItemInfo() const { return infosFromItems(d->rootItem->children); } TableViewModel::GroupingMode TableViewModel::groupingMode() const { return d->groupingMode; } void TableViewModel::setGroupingMode(const TableViewModel::GroupingMode newGroupingMode) { if (d->groupingMode != newGroupingMode) { d->groupingMode = newGroupingMode; QTimer::singleShot(100, this, SLOT(slotPopulateModelWithNotifications())); emit signalGroupingModeChanged(); } } QModelIndex TableViewModel::deepRowIndex(const int rowNumber) const { int targetRowNumber = rowNumber; if (rowNumber < 0) { targetRowNumber += deepRowCount(); } QModelIndex cIndex = index(0, 0); for (int i = 0 ; i < targetRowNumber ; ++i) { if (hasChildren(cIndex)) { cIndex = index(0, 0, cIndex); } else { QModelIndex candidateIndex = cIndex.sibling(cIndex.row() + 1, 0); if (!candidateIndex.isValid()) { QModelIndex parentIndex = cIndex.parent(); if (!parentIndex.isValid()) { return QModelIndex(); } candidateIndex = parentIndex.sibling(parentIndex.row() + 1, 0); } cIndex = candidateIndex; } } return cIndex; } int TableViewModel::indexToDeepRowNumber(const QModelIndex& rowIndex) const { const QModelIndex column0Index = toCol0(rowIndex); int deepRowNumber = 0; QModelIndex cIndex = index(0, 0); while (cIndex.isValid()) { if (cIndex == column0Index) { break; } ++deepRowNumber; if (hasChildren(cIndex)) { cIndex = index(0, 0, cIndex); } else { QModelIndex candidateIndex = cIndex.sibling(cIndex.row() + 1, 0); if (!candidateIndex.isValid()) { QModelIndex parentIndex = cIndex.parent(); if (!parentIndex.isValid()) { break; } candidateIndex = parentIndex.sibling(parentIndex.row() + 1, 0); } cIndex = candidateIndex; } } if (!cIndex.isValid()) { - return -1; + return (-1); } return deepRowNumber; } int TableViewModel::deepRowCount() const { int deepRowNumber = 0; QModelIndex cIndex = index(0, 0); while (cIndex.isValid()) { ++deepRowNumber; if (hasChildren(cIndex)) { cIndex = index(0, 0, cIndex); } else { QModelIndex candidateIndex = cIndex.sibling(cIndex.row() + 1, 0); if (!candidateIndex.isValid()) { QModelIndex parentIndex = cIndex.parent(); if (!parentIndex.isValid()) { break; } candidateIndex = parentIndex.sibling(parentIndex.row() + 1, 0); } cIndex = candidateIndex; } } return deepRowNumber; } QModelIndex TableViewModel::toCol0(const QModelIndex& anIndex) const { return anIndex.sibling(anIndex.row(), 0); } int TableViewModel::firstDeepRowNotInList(const QList& needleList) { int currentNeedlePos = 0; QModelIndex currentNeedleIndex = toCol0(needleList.first()); int deepRowNumber = 0; QModelIndex cIndex = index(0, 0); while (cIndex.isValid()) { if (cIndex != currentNeedleIndex) { return deepRowNumber; } if (hasChildren(cIndex)) { cIndex = index(0, 0, cIndex); } else { QModelIndex candidateIndex = cIndex.sibling(cIndex.row() + 1, 0); if (!candidateIndex.isValid()) { QModelIndex parentIndex = cIndex.parent(); if (!parentIndex.isValid()) { break; } candidateIndex = parentIndex.sibling(parentIndex.row() + 1, 0); } cIndex = candidateIndex; } if (cIndex.isValid()) { ++deepRowNumber; ++currentNeedlePos; if (currentNeedlePos >= needleList.count()) { return deepRowNumber; } currentNeedleIndex = toCol0(needleList.at(currentNeedlePos)); } } return -1; } void TableViewModel::slotSetActive(const bool isActive) { if (isActive) { if (d->outdated) { // populate the model once later, not now QTimer::singleShot(0, this, SLOT(slotPopulateModelWithNotifications())); } } } int TableViewModel::findChildSortedPosition(TableViewModel::Item* const parentItem, TableViewModel::Item* const childItem) { if (parentItem->children.isEmpty()) { return 0; } // nChildren is guaranteed to be >=1 const int nChildren = parentItem->children.count(); int stepSize = nChildren / 2; // make sure pos is at least 0 if there is only one item int pos = qMin(nChildren - 1, stepSize); while (true) { stepSize = stepSize / 2; if (stepSize == 0) { stepSize = 1; } bool isLessThanUpper = lessThan(childItem, parentItem->children.at(pos)); if (d->sortOrder == Qt::DescendingOrder) { isLessThanUpper = !isLessThanUpper; } if (!isLessThanUpper) { // need to jump up, quit if we can not jump up by 1 if ((pos + 1) >= nChildren) { pos = nChildren; break; } // jump up by stepSize and make sure we do not jump over the end pos += stepSize; if (pos >= nChildren) { pos = nChildren - 1; } continue; } // can we go lower? const bool lowerThere = (pos > 0); if (!lowerThere) { // no, stop pos = 0; break; } bool isLessThanLower = lessThan(childItem, parentItem->children.at(pos-1)); if (d->sortOrder == Qt::DescendingOrder) { isLessThanLower = !isLessThanLower; } if (isLessThanLower) { // go lower and make sure we do not jump too low pos -= stepSize; if (pos < 0) { pos = 0; } continue; } break; } return pos; } } // namespace Digikam