File indexing completed on 2024-04-28 04:21:19

0001 // SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team
0002 // SPDX-FileCopyrightText: 2022 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 //
0004 // SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006 #include "FileVersionDetectionPage.h"
0007 
0008 #include <kpabase/SettingsData.h>
0009 
0010 #include <KComboBox>
0011 #include <KLocalizedString>
0012 #include <QCheckBox>
0013 #include <QGroupBox>
0014 #include <QLabel>
0015 #include <QLineEdit>
0016 #include <QScrollArea>
0017 #include <QSpinBox>
0018 #include <QVBoxLayout>
0019 
0020 Settings::FileVersionDetectionPage::FileVersionDetectionPage(QWidget *parent)
0021     : QWidget(parent)
0022 {
0023     auto *mainLayout = new QVBoxLayout(this);
0024     auto *scrollWidget = new QWidget;
0025     auto *topLayout = new QVBoxLayout(scrollWidget);
0026 
0027     QString txt;
0028 
0029     // General file searching
0030     {
0031         QGroupBox *generalBox = new QGroupBox(i18n("New File Searches"), this);
0032         topLayout->addWidget(generalBox);
0033         QVBoxLayout *layout = new QVBoxLayout(generalBox);
0034 
0035         // Search for images on startup
0036         m_searchForImagesOnStart = new QCheckBox(i18n("Search for new images and videos on startup"), generalBox);
0037         layout->addWidget(m_searchForImagesOnStart);
0038 
0039         m_ignoreFileExtension = new QCheckBox(i18n("Ignore file extensions when searching for new images and videos"), generalBox);
0040         layout->addWidget(m_ignoreFileExtension);
0041 
0042         m_skipSymlinks = new QCheckBox(i18n("Skip symbolic links when searching for new images"), generalBox);
0043         layout->addWidget(m_skipSymlinks);
0044 
0045         m_skipRawIfOtherMatches = new QCheckBox(i18n("Do not read RAW files if a matching JPEG/TIFF file exists"), generalBox);
0046         layout->addWidget(m_skipRawIfOtherMatches);
0047 
0048         // Exclude directories from search
0049         QLabel *excludeDirectoriesLabel = new QLabel(i18n("Directories to exclude from new file search:"), generalBox);
0050         layout->addWidget(excludeDirectoriesLabel);
0051 
0052         m_excludeDirectories = new QLineEdit(generalBox);
0053         layout->addWidget(m_excludeDirectories);
0054         excludeDirectoriesLabel->setBuddy(m_excludeDirectories);
0055 
0056         txt = i18n("<p>Specify a comma-separated list of folder names to ignore.</p>"
0057                    "<p>For example, specifying \"<tt>.thumbs,.thumbnails</tt>\" here will cause "
0058                    "KPhotoAlbum to ignore these two folder names in any folder when searching for new images.</p>");
0059         m_excludeDirectories->setWhatsThis(txt);
0060 
0061         txt = i18n("<p>KPhotoAlbum is capable of searching for new images and videos when started, this does, "
0062                    "however, take some time, so instead you may wish to manually tell KPhotoAlbum to search for new images "
0063                    "using <b>Maintenance->Rescan for new images</b></p>");
0064         m_searchForImagesOnStart->setWhatsThis(txt);
0065 
0066         txt = i18n("<p>KPhotoAlbum will normally search new images and videos by their file extension. "
0067                    "If this option is set, <em>all</em> files neither in the database nor in the block list "
0068                    "will be checked by their Mime type, regardless of their extension. This will take "
0069                    "significantly longer than finding files by extension!</p>");
0070         m_ignoreFileExtension->setWhatsThis(txt);
0071 
0072         txt = i18n("<p>KPhotoAlbum attempts to read all image files whether actual files or symbolic links. If you "
0073                    "wish to ignore symbolic links, check this option. This is useful if for some reason you have e.g. "
0074                    "both the original files and symbolic links to these files within your image folder.</p>");
0075         m_skipSymlinks->setWhatsThis(txt);
0076 
0077         txt = i18n("<p>KPhotoAlbum is capable of reading certain kinds of RAW images. "
0078                    "Some cameras store both a RAW image and a matching JPEG or TIFF image. "
0079                    "This causes duplicate images to be stored in KPhotoAlbum, which may be undesirable. "
0080                    "If this option is checked, KPhotoAlbum will not read RAW files for which matching image files also exist.</p>");
0081         m_skipRawIfOtherMatches->setWhatsThis(txt);
0082 
0083         txt = i18n("<p>Directories defined here (separated by comma <tt>,</tt>) are "
0084                    "skipped when searching for new photos. Thumbnail directories of different "
0085                    "tools should be configured here. E.g. <tt>xml,ThumbNails,.thumbs,.thumbnails</tt>.</p>");
0086         excludeDirectoriesLabel->setWhatsThis(txt);
0087     }
0088 
0089     // Original/Modified File Support
0090     {
0091         QGroupBox *modifiedBox = new QGroupBox(i18n("File Version Detection Settings"), this);
0092         topLayout->addWidget(modifiedBox);
0093         QVBoxLayout *layout = new QVBoxLayout(modifiedBox);
0094 
0095         m_detectModifiedFiles = new QCheckBox(i18n("Try to detect multiple versions of files"), modifiedBox);
0096         layout->addWidget(m_detectModifiedFiles);
0097 
0098         QLabel *modifiedFileComponentLabel = new QLabel(i18n("File versions search regexp:"), modifiedBox);
0099         layout->addWidget(modifiedFileComponentLabel);
0100 
0101         m_modifiedFileComponent = new QLineEdit(modifiedBox);
0102         layout->addWidget(m_modifiedFileComponent);
0103 
0104         QLabel *originalFileComponentLabel = new QLabel(i18n("Original file replacement text:"), modifiedBox);
0105         layout->addWidget(originalFileComponentLabel);
0106 
0107         m_originalFileComponent = new QLineEdit(modifiedBox);
0108         layout->addWidget(m_originalFileComponent);
0109 
0110         m_moveOriginalContents = new QCheckBox(i18n("Move meta-data (i.e. delete tags from the original)"), modifiedBox);
0111         layout->addWidget(m_moveOriginalContents);
0112 
0113         m_autoStackNewFiles = new QCheckBox(i18n("Automatically stack new versions of images"), modifiedBox);
0114         layout->addWidget(m_autoStackNewFiles);
0115 
0116         txt = i18n("<p>When KPhotoAlbum searches for new files and finds a file that matches the "
0117                    "<i>modified file search regexp</i> it is assumed that an original version of "
0118                    "the image may exist. The regexp pattern will be replaced with the <i>original "
0119                    "file replacement text</i> and if that file exists, all associated metadata (category "
0120                    "information, ratings, etc) will be copied or moved from the original file to the new one.</p>");
0121         m_detectModifiedFiles->setWhatsThis(txt);
0122 
0123         txt = i18n("<p>A perl regular expression that should match a modified file. "
0124                    "<ul><li>A dot matches a single character (<tt>\\.</tt> matches a dot)</li> "
0125                    "  <li>You can use the quantifiers <tt>*</tt>,<tt>+</tt>,<tt>?</tt>, or you can "
0126                    "    match multiple occurrences of an expression by using curly brackets (e.g. "
0127                    "<tt>e{0,1}</tt> matches 0 or 1 occurrences of the character \"e\").</li> "
0128                    "  <li>You can group parts of the expression using parenthesis.</li> "
0129                    "</ul>Example: <tt>-modified\\.(jpg|tiff)</tt> </p>");
0130         modifiedFileComponentLabel->setWhatsThis(txt);
0131         m_modifiedFileComponent->setWhatsThis(txt);
0132 
0133         txt = i18n("<p>A string that is used to replace the match from the <i>File versions search regexp</i>. "
0134                    "This can be a semicolon (<tt>;</tt>) separated list. Each string is used to replace the match "
0135                    "in the new file's name until an original file is found or we run out of options.</p>");
0136         originalFileComponentLabel->setWhatsThis(txt);
0137         m_originalFileComponent->setWhatsThis(txt);
0138 
0139         txt = i18n("<p>The tagging is moved from the original file to the new file. This way "
0140                    "only the latest version of an image is tagged.</p>");
0141         m_moveOriginalContents->setWhatsThis(txt);
0142 
0143         txt = i18n("<p>If this option is set, new versions of an image are automatically stacked "
0144                    "and placed to the top of the stack. This way the new image is shown when the "
0145                    "stack is in collapsed state - the default state in KPhotoAlbum.</p>");
0146         m_autoStackNewFiles->setWhatsThis(txt);
0147     }
0148 
0149     // Copy File Support
0150     {
0151         QGroupBox *copyBox = new QGroupBox(i18nc("Configure the feature to make a copy of a file first and then open the copied file with an external application", "Copy File and Open with an External Application"), this);
0152         topLayout->addWidget(copyBox);
0153         QVBoxLayout *layout = new QVBoxLayout(copyBox);
0154 
0155         QLabel *copyFileComponentLabel = new QLabel(i18n("Copy file search regexp:"), copyBox);
0156         layout->addWidget(copyFileComponentLabel);
0157 
0158         m_copyFileComponent = new QLineEdit(copyBox);
0159         layout->addWidget(m_copyFileComponent);
0160 
0161         QLabel *copyFileReplacementComponentLabel = new QLabel(i18n("Copy file replacement text:"), copyBox);
0162         layout->addWidget(copyFileReplacementComponentLabel);
0163 
0164         m_copyFileReplacementComponent = new QLineEdit(copyBox);
0165         layout->addWidget(m_copyFileReplacementComponent);
0166 
0167         txt = i18n("<p>KPhotoAlbum can make a copy of an image before opening it with an external application. This configuration defines how the new file is named.</p>"
0168                    "<p>The regular expression defines the part of the original file name that is replaced with the <i>replacement text</i>. "
0169                    "E.g. regexp <i>\"\\.(jpg|png)\"</i> and replacement text <i>\"-mod.\\1\"</i> would copy test.jpg to test-mod.jpg and open the new file in selected application.</p>");
0170         copyFileComponentLabel->setWhatsThis(txt);
0171         m_copyFileComponent->setWhatsThis(txt);
0172         copyFileReplacementComponentLabel->setWhatsThis(txt);
0173         m_copyFileReplacementComponent->setWhatsThis(txt);
0174     }
0175 
0176     // Loader Optimization Setting (prototype)
0177     {
0178         QGroupBox *loadOptimizationBox = new QGroupBox(i18n("EXPERIMENTAL: Tune new image loading for best performance."), this);
0179         topLayout->addWidget(loadOptimizationBox);
0180         QGridLayout *layout = new QGridLayout(loadOptimizationBox);
0181         int row = 0;
0182 
0183         // Loader preset
0184         QLabel *loadOptimizationPresetLabel = new QLabel(i18n("Type of media on which your images are stored."));
0185         layout->addWidget(loadOptimizationPresetLabel, row, 0);
0186         layout->setColumnStretch(0, 1);
0187 
0188         m_loadOptimizationPreset = new KComboBox;
0189         m_loadOptimizationPreset->addItems(QStringList() << i18n("Hard Disk")
0190                                                          << i18n("Network")
0191                                                          << i18n("SATA SSD")
0192                                                          << i18n("Slow PCIe/NVMe")
0193                                                          << i18n("Fast PCIe/NVMe")
0194                                                          << i18n("Manual Settings")); // manual is expected to be the last item
0195         layout->addWidget(m_loadOptimizationPreset, row, 1);
0196 
0197         txt = i18n("<p>Tune image loading for best performance based on the type of storage your image database resides on.  If your image database resides on multiple media, choose the slowest media type used.</p>"
0198                    "<p>Use Manual Settings to configure details of how the loading is performed.</p>");
0199         loadOptimizationPresetLabel->setWhatsThis(txt);
0200         m_loadOptimizationPreset->setWhatsThis(txt);
0201         connect(m_loadOptimizationPreset, QOverload<int>::of(&QComboBox::activated), this, &FileVersionDetectionPage::slotUpdateOptimizationUI);
0202 
0203         // Overlap load with MD5 computation
0204         ++row;
0205         QLabel *overlapLoadMD5Label = new QLabel(i18n("Calculate MD5 checksum while images are being preloaded"));
0206         layout->addWidget(overlapLoadMD5Label, row, 0);
0207         m_overlapLoadMD5 = new QCheckBox;
0208         layout->addWidget(m_overlapLoadMD5, row, 1);
0209 
0210         txt = i18n("<p>Calculate MD5 checksums while images are being preloaded.  This works well if the storage is very fast, such as an NVMe drive.  If the storage is slow, this degrades performance as the checksum calculation has to wait longer for the images to be preloaded.</p>");
0211         overlapLoadMD5Label->setWhatsThis(txt);
0212         m_overlapLoadMD5->setWhatsThis(txt);
0213 
0214         // Threads for preload
0215         ++row;
0216         QLabel *preloadThreadCountLabel = new QLabel(i18n("Number of threads to use for preloading (scouting) images"));
0217         layout->addWidget(preloadThreadCountLabel, row, 0);
0218 
0219         m_preloadThreadCount = new QSpinBox;
0220         m_preloadThreadCount->setRange(1, 16);
0221         m_preloadThreadCount->setSingleStep(1);
0222         layout->addWidget(m_preloadThreadCount, row, 1);
0223 
0224         txt = i18n("<p>Number of threads to use for preloading images to have them in memory when their checksums are calculated.  This should generally be set higher for faster storage, but not more than the number of cores in your CPU. Default is 1, which works well for mechanical hard disks.</p>");
0225         preloadThreadCountLabel->setWhatsThis(txt);
0226         m_preloadThreadCount->setWhatsThis(txt);
0227 
0228         // Threads for thumbnailPreload
0229         ++row;
0230         QLabel *thumbnailPreloadThreadCountLabel = new QLabel(i18n("Number of threads to use for thumbnailPreloading (scouting) images"));
0231         layout->addWidget(thumbnailPreloadThreadCountLabel, row, 0);
0232 
0233         m_thumbnailPreloadThreadCount = new QSpinBox;
0234         m_thumbnailPreloadThreadCount->setRange(1, 16);
0235         m_thumbnailPreloadThreadCount->setSingleStep(1);
0236         layout->addWidget(m_thumbnailPreloadThreadCount, row, 1);
0237 
0238         txt = i18n("<p>Number of threads to use for preloading images prior to building thumbnails.  Normally this should be set to 1; the exception might be if you have very fast storage.</p>");
0239         thumbnailPreloadThreadCountLabel->setWhatsThis(txt);
0240         m_thumbnailPreloadThreadCount->setWhatsThis(txt);
0241 
0242         // Threads for thumbnailBuilder
0243         ++row;
0244         QLabel *thumbnailBuilderThreadCountLabel = new QLabel(i18n("Number of threads to use for building thumbnails"));
0245         layout->addWidget(thumbnailBuilderThreadCountLabel, row, 0);
0246 
0247         m_thumbnailBuilderThreadCount = new QSpinBox(loadOptimizationBox);
0248         m_thumbnailBuilderThreadCount->setRange(0, 16);
0249         m_thumbnailBuilderThreadCount->setSingleStep(1);
0250         layout->addWidget(m_thumbnailBuilderThreadCount, row, 1);
0251 
0252         txt = i18n("<p>Number of threads to use for building thumbnails.  If set to 0 this will be set automatically one less than the number of cores, at least one and no more than three.  If you have fast storage and a CPU with many cores, you may see benefit from setting this to a larger value.</p>"
0253                    "<p>KPhotoAlbum must be restarted for changes to take effect.</p>");
0254         thumbnailBuilderThreadCountLabel->setWhatsThis(txt);
0255         m_thumbnailBuilderThreadCount->setWhatsThis(txt);
0256     }
0257 
0258     auto *scrollArea = new QScrollArea;
0259     mainLayout->addWidget(scrollArea);
0260     scrollArea->setWidget(scrollWidget);
0261 }
0262 
0263 Settings::FileVersionDetectionPage::~FileVersionDetectionPage()
0264 {
0265     delete m_searchForImagesOnStart;
0266     delete m_ignoreFileExtension;
0267     delete m_skipSymlinks;
0268     delete m_skipRawIfOtherMatches;
0269     delete m_excludeDirectories;
0270     delete m_detectModifiedFiles;
0271     delete m_modifiedFileComponent;
0272     delete m_originalFileComponent;
0273     delete m_moveOriginalContents;
0274     delete m_autoStackNewFiles;
0275     delete m_copyFileComponent;
0276     delete m_copyFileReplacementComponent;
0277     delete m_loadOptimizationPreset;
0278     delete m_overlapLoadMD5;
0279     delete m_preloadThreadCount;
0280     delete m_thumbnailPreloadThreadCount;
0281     delete m_thumbnailBuilderThreadCount;
0282 }
0283 
0284 void Settings::FileVersionDetectionPage::loadSettings(Settings::SettingsData *opt)
0285 {
0286     m_searchForImagesOnStart->setChecked(opt->searchForImagesOnStart());
0287     m_ignoreFileExtension->setChecked(opt->ignoreFileExtension());
0288     m_skipSymlinks->setChecked(opt->skipSymlinks());
0289     m_skipRawIfOtherMatches->setChecked(opt->skipRawIfOtherMatches());
0290     m_excludeDirectories->setText(opt->excludeDirectories());
0291     m_detectModifiedFiles->setChecked(opt->detectModifiedFiles());
0292     m_modifiedFileComponent->setText(opt->modifiedFileComponent());
0293     m_originalFileComponent->setText(opt->originalFileComponent());
0294     m_moveOriginalContents->setChecked(opt->moveOriginalContents());
0295     m_autoStackNewFiles->setChecked(opt->autoStackNewFiles());
0296     m_copyFileComponent->setText(opt->copyFileComponent());
0297     m_copyFileReplacementComponent->setText(opt->copyFileReplacementComponent());
0298     m_loadOptimizationPreset->setCurrentIndex(opt->loadOptimizationPreset());
0299     m_overlapLoadMD5->setChecked(opt->overlapLoadMD5());
0300     m_preloadThreadCount->setValue(opt->preloadThreadCount());
0301     m_thumbnailPreloadThreadCount->setValue(opt->thumbnailPreloadThreadCount());
0302     m_thumbnailBuilderThreadCount->setValue(opt->thumbnailBuilderThreadCount());
0303     slotUpdateOptimizationUI();
0304 }
0305 
0306 void Settings::FileVersionDetectionPage::saveSettings(Settings::SettingsData *opt)
0307 {
0308     opt->setSearchForImagesOnStart(m_searchForImagesOnStart->isChecked());
0309     opt->setIgnoreFileExtension(m_ignoreFileExtension->isChecked());
0310     opt->setSkipSymlinks(m_skipSymlinks->isChecked());
0311     opt->setSkipRawIfOtherMatches(m_skipRawIfOtherMatches->isChecked());
0312     opt->setExcludeDirectories(m_excludeDirectories->text());
0313     opt->setDetectModifiedFiles(m_detectModifiedFiles->isChecked());
0314     opt->setModifiedFileComponent(m_modifiedFileComponent->text());
0315     opt->setOriginalFileComponent(m_originalFileComponent->text());
0316     opt->setAutoStackNewFiles(m_autoStackNewFiles->isChecked());
0317     opt->setCopyFileComponent(m_copyFileComponent->text());
0318     opt->setCopyFileReplacementComponent(m_copyFileReplacementComponent->text());
0319     opt->setLoadOptimizationPreset(m_loadOptimizationPreset->currentIndex());
0320     opt->setOverlapLoadMD5(m_overlapLoadMD5->isChecked());
0321     opt->setPreloadThreadCount(m_preloadThreadCount->value());
0322     opt->setThumbnailPreloadThreadCount(m_thumbnailPreloadThreadCount->value());
0323     opt->setThumbnailBuilderThreadCount(m_thumbnailBuilderThreadCount->value());
0324 }
0325 
0326 void Settings::FileVersionDetectionPage::slotUpdateOptimizationUI()
0327 {
0328     const bool manualModeIsSelected = (m_loadOptimizationPreset->currentIndex() + 1 == m_loadOptimizationPreset->count());
0329     m_overlapLoadMD5->setEnabled(manualModeIsSelected);
0330     m_preloadThreadCount->setEnabled(manualModeIsSelected);
0331     m_thumbnailPreloadThreadCount->setEnabled(manualModeIsSelected);
0332     m_thumbnailBuilderThreadCount->setEnabled(manualModeIsSelected);
0333 }
0334 // vi:expandtab:tabstop=4 shiftwidth=4: