File indexing completed on 2024-04-28 15:40:11
0001 /* SPDX-FileCopyrightText: 2010-2020 The KPhotoAlbum Development Team 0002 0003 SPDX-License-Identifier: GPL-2.0-or-later 0004 */ 0005 0006 #include "AutoStackImages.h" 0007 0008 #include "Window.h" 0009 0010 #include <DB/FileInfo.h> 0011 #include <DB/ImageDB.h> 0012 #include <DB/ImageDate.h> 0013 #include <DB/ImageInfo.h> 0014 #include <DB/MD5Map.h> 0015 #include <Utilities/FileUtil.h> 0016 #include <Utilities/ShowBusyCursor.h> 0017 #include <kpabase/SettingsData.h> 0018 0019 #include <KLocalizedString> 0020 #include <QApplication> 0021 #include <QCheckBox> 0022 #include <QDialogButtonBox> 0023 #include <QEventLoop> 0024 #include <QGroupBox> 0025 #include <QLabel> 0026 #include <QLayout> 0027 #include <QPushButton> 0028 #include <QRadioButton> 0029 #include <QSpinBox> 0030 #include <QVBoxLayout> 0031 0032 using namespace MainWindow; 0033 0034 AutoStackImages::AutoStackImages(QWidget *parent, const DB::FileNameList &list) 0035 : QDialog(parent) 0036 , m_list(list) 0037 { 0038 setWindowTitle(i18nc("@title:window", "Automatically Stack Images")); 0039 0040 QWidget *top = new QWidget; 0041 QVBoxLayout *lay1 = new QVBoxLayout(top); 0042 setLayout(lay1); 0043 0044 QWidget *containerMd5 = new QWidget(this); 0045 lay1->addWidget(containerMd5); 0046 QHBoxLayout *hlayMd5 = new QHBoxLayout(containerMd5); 0047 0048 m_matchingMD5 = new QCheckBox(i18n("Stack images with identical MD5 sum")); 0049 m_matchingMD5->setChecked(false); 0050 hlayMd5->addWidget(m_matchingMD5); 0051 0052 QWidget *containerFile = new QWidget(this); 0053 lay1->addWidget(containerFile); 0054 QHBoxLayout *hlayFile = new QHBoxLayout(containerFile); 0055 0056 m_matchingFile = new QCheckBox(i18n("Stack images based on file version detection")); 0057 m_matchingFile->setChecked(true); 0058 hlayFile->addWidget(m_matchingFile); 0059 0060 m_origTop = new QCheckBox(i18n("Original to top")); 0061 m_origTop->setChecked(false); 0062 hlayFile->addWidget(m_origTop); 0063 0064 QWidget *containerContinuous = new QWidget(this); 0065 lay1->addWidget(containerContinuous); 0066 QHBoxLayout *hlayContinuous = new QHBoxLayout(containerContinuous); 0067 0068 // Would minutes not be a more sane time unit here? (schwarzer) 0069 // jzarl: this feature is used to group "burst" images - usually they are taken within a few seconds. 0070 // a typical value would be somewhere between 2 and 20 seconds 0071 m_continuousShooting = new QCheckBox(i18nc("Label for a QSpinbox that selects a number of seconds.", "Stack images created within the following time-frame:")); 0072 m_continuousShooting->setChecked(false); 0073 hlayContinuous->addWidget(m_continuousShooting); 0074 0075 m_continuousThreshold = new QSpinBox; 0076 m_continuousThreshold->setRange(1, 999); 0077 m_continuousThreshold->setSingleStep(1); 0078 m_continuousThreshold->setValue(2); 0079 m_continuousThreshold->setSuffix(i18nc("Unit suffix on a QSpinBox; note the space at the beginning.", " seconds")); 0080 hlayContinuous->addWidget(m_continuousThreshold); 0081 0082 QGroupBox *grpOptions = new QGroupBox(i18n("AutoStacking Options")); 0083 QVBoxLayout *grpLayOptions = new QVBoxLayout(grpOptions); 0084 lay1->addWidget(grpOptions); 0085 0086 m_autostackDefault = new QRadioButton(i18n("Include matching image to appropriate stack (if one exists)")); 0087 m_autostackDefault->setChecked(true); 0088 grpLayOptions->addWidget(m_autostackDefault); 0089 0090 m_autostackUnstack = new QRadioButton(i18n("Unstack images from their current stack and create new one for the matches")); 0091 m_autostackUnstack->setChecked(false); 0092 grpLayOptions->addWidget(m_autostackUnstack); 0093 0094 m_autostackSkip = new QRadioButton(i18n("Skip images that are already in a stack")); 0095 m_autostackSkip->setChecked(false); 0096 grpLayOptions->addWidget(m_autostackSkip); 0097 0098 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 0099 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); 0100 okButton->setDefault(true); 0101 okButton->setShortcut(Qt::CTRL | Qt::Key_Return); 0102 connect(buttonBox, &QDialogButtonBox::accepted, this, &AutoStackImages::accept); 0103 connect(buttonBox, &QDialogButtonBox::rejected, this, &AutoStackImages::reject); 0104 lay1->addWidget(buttonBox); 0105 } 0106 0107 /* 0108 * This function searches for images with matching MD5 sums 0109 * Matches are automatically stacked 0110 */ 0111 0112 void AutoStackImages::matchingMD5(DB::FileNameList &toBeShown) 0113 { 0114 QMap<DB::MD5, DB::FileNameList> tostack; 0115 DB::FileNameList showIfStacked; 0116 0117 // Stacking all images that have the same MD5 sum 0118 // First make a map of MD5 sums with corresponding images 0119 for (const DB::FileName &fileName : qAsConst(m_list)) { 0120 const auto info = DB::ImageDB::instance()->info(fileName); 0121 DB::MD5 sum = info->MD5Sum(); 0122 if (DB::ImageDB::instance()->md5Map()->contains(sum)) { 0123 if (tostack[sum].isEmpty()) 0124 tostack.insert(sum, DB::FileNameList() << fileName); 0125 else 0126 tostack[sum].append(fileName); 0127 } 0128 } 0129 0130 // Then add images to stack (depending on configuration options) 0131 for (QMap<DB::MD5, DB::FileNameList>::ConstIterator it = tostack.constBegin(); it != tostack.constEnd(); ++it) { 0132 if (tostack[it.key()].count() > 1) { 0133 DB::FileNameList stack; 0134 for (int i = 0; i < tostack[it.key()].count(); ++i) { 0135 if (!DB::ImageDB::instance()->getStackFor(tostack[it.key()][i]).isEmpty()) { 0136 if (m_autostackUnstack->isChecked()) 0137 DB::ImageDB::instance()->unstack(DB::FileNameList() << tostack[it.key()][i]); 0138 else if (m_autostackSkip->isChecked()) 0139 continue; 0140 } 0141 0142 showIfStacked.append(tostack[it.key()][i]); 0143 stack.append(tostack[it.key()][i]); 0144 } 0145 if (stack.size() > 1) { 0146 0147 for (const DB::FileName &a : qAsConst(showIfStacked)) { 0148 if (!DB::ImageDB::instance()->getStackFor(a).isEmpty()) { 0149 const auto stackedImages = DB::ImageDB::instance()->getStackFor(a); 0150 for (const DB::FileName &b : stackedImages) 0151 toBeShown.append(b); 0152 } else 0153 toBeShown.append(a); 0154 } 0155 DB::ImageDB::instance()->stack(stack); 0156 } 0157 showIfStacked.clear(); 0158 } 0159 } 0160 } 0161 0162 /* 0163 * This function searches for images based on file version detection configuration. 0164 * Images that are detected to be versions of same file are stacked together. 0165 */ 0166 0167 void AutoStackImages::matchingFile(DB::FileNameList &toBeShown) 0168 { 0169 QMap<DB::MD5, DB::FileNameList> tostack; 0170 DB::FileNameList showIfStacked; 0171 QString modifiedFileCompString; 0172 QRegExp modifiedFileComponent; 0173 QStringList originalFileComponents; 0174 0175 modifiedFileCompString = Settings::SettingsData::instance()->modifiedFileComponent(); 0176 modifiedFileComponent = QRegExp(modifiedFileCompString); 0177 0178 originalFileComponents << Settings::SettingsData::instance()->originalFileComponent(); 0179 originalFileComponents = originalFileComponents.at(0).split(QString::fromLatin1(";")); 0180 0181 // Stacking all images based on file version detection 0182 // First round prepares the stacking 0183 for (const DB::FileName &fileName : qAsConst(m_list)) { 0184 if (modifiedFileCompString.length() >= 0 && fileName.relative().contains(modifiedFileComponent)) { 0185 0186 for (QStringList::const_iterator it = originalFileComponents.constBegin(); 0187 it != originalFileComponents.constEnd(); ++it) { 0188 QString tmp = fileName.relative(); 0189 tmp.replace(modifiedFileComponent, (*it)); 0190 DB::FileName originalFileName = DB::FileName::fromRelativePath(tmp); 0191 0192 if (originalFileName != fileName && m_list.contains(originalFileName)) { 0193 const auto info = DB::ImageDB::instance()->info(fileName); 0194 DB::MD5 sum = info->MD5Sum(); 0195 if (tostack[sum].isEmpty()) { 0196 if (m_origTop->isChecked()) { 0197 tostack.insert(sum, DB::FileNameList() << originalFileName); 0198 tostack[sum].append(fileName); 0199 } else { 0200 tostack.insert(sum, DB::FileNameList() << fileName); 0201 tostack[sum].append(originalFileName); 0202 } 0203 } else 0204 tostack[sum].append(fileName); 0205 break; 0206 } 0207 } 0208 } 0209 } 0210 0211 // Then add images to stack (depending on configuration options) 0212 for (QMap<DB::MD5, DB::FileNameList>::ConstIterator it = tostack.constBegin(); it != tostack.constEnd(); ++it) { 0213 if (tostack[it.key()].count() > 1) { 0214 DB::FileNameList stack; 0215 for (int i = 0; i < tostack[it.key()].count(); ++i) { 0216 if (!DB::ImageDB::instance()->getStackFor(tostack[it.key()][i]).isEmpty()) { 0217 if (m_autostackUnstack->isChecked()) 0218 DB::ImageDB::instance()->unstack(DB::FileNameList() << tostack[it.key()][i]); 0219 else if (m_autostackSkip->isChecked()) 0220 continue; 0221 } 0222 0223 showIfStacked.append(tostack[it.key()][i]); 0224 stack.append(tostack[it.key()][i]); 0225 } 0226 if (stack.size() > 1) { 0227 0228 for (const DB::FileName &a : qAsConst(showIfStacked)) { 0229 if (!DB::ImageDB::instance()->getStackFor(a).isEmpty()) { 0230 const auto stackedImages = DB::ImageDB::instance()->getStackFor(a); 0231 for (const DB::FileName &b : stackedImages) 0232 toBeShown.append(b); 0233 } else 0234 toBeShown.append(a); 0235 } 0236 DB::ImageDB::instance()->stack(stack); 0237 } 0238 showIfStacked.clear(); 0239 } 0240 } 0241 } 0242 0243 /** 0244 * This function searches for images that are shot within specified time frame 0245 */ 0246 void AutoStackImages::continuousShooting(DB::FileNameList &toBeShown) 0247 { 0248 DB::ImageInfoPtr prev; 0249 for (const DB::FileName &fileName : qAsConst(m_list)) { 0250 const auto info = DB::ImageDB::instance()->info(fileName); 0251 // Skipping images that do not have exact time stamp 0252 if (info->date().start() != info->date().end()) 0253 continue; 0254 if (prev && (prev->date().start().secsTo(info->date().start()) < m_continuousThreshold->value())) { 0255 DB::FileNameList stack; 0256 0257 if (!DB::ImageDB::instance()->getStackFor(prev->fileName()).isEmpty()) { 0258 if (m_autostackUnstack->isChecked()) 0259 DB::ImageDB::instance()->unstack(DB::FileNameList() << prev->fileName()); 0260 else if (m_autostackSkip->isChecked()) 0261 continue; 0262 } 0263 0264 if (!DB::ImageDB::instance()->getStackFor(fileName).isEmpty()) { 0265 if (m_autostackUnstack->isChecked()) 0266 DB::ImageDB::instance()->unstack(DB::FileNameList() << fileName); 0267 else if (m_autostackSkip->isChecked()) 0268 continue; 0269 } 0270 0271 stack.append(prev->fileName()); 0272 stack.append(info->fileName()); 0273 if (!toBeShown.isEmpty()) { 0274 if (toBeShown.at(toBeShown.size() - 1) != prev->fileName()) 0275 toBeShown.append(prev->fileName()); 0276 } else { 0277 // if this is first insert, we have to include also the stacked images from previuous image 0278 if (!DB::ImageDB::instance()->getStackFor(info->fileName()).isEmpty()) { 0279 const auto stackedImages = DB::ImageDB::instance()->getStackFor(prev->fileName()); 0280 for (const DB::FileName &a : stackedImages) 0281 toBeShown.append(a); 0282 } else 0283 toBeShown.append(prev->fileName()); 0284 } 0285 // Inserting stacked images from the current image 0286 if (!DB::ImageDB::instance()->getStackFor(info->fileName()).isEmpty()) { 0287 const auto stackedImages = DB::ImageDB::instance()->getStackFor(fileName); 0288 for (const DB::FileName &a : stackedImages) 0289 toBeShown.append(a); 0290 } else 0291 toBeShown.append(info->fileName()); 0292 DB::ImageDB::instance()->stack(stack); 0293 } 0294 0295 prev = info; 0296 } 0297 } 0298 0299 void AutoStackImages::accept() 0300 { 0301 QDialog::accept(); 0302 Utilities::ShowBusyCursor dummy; 0303 DB::FileNameList toBeShown; 0304 0305 if (m_matchingMD5->isChecked()) 0306 matchingMD5(toBeShown); 0307 if (m_matchingFile->isChecked()) 0308 matchingFile(toBeShown); 0309 if (m_continuousShooting->isChecked()) 0310 continuousShooting(toBeShown); 0311 0312 MainWindow::Window::theMainWindow()->showThumbNails(toBeShown); 0313 } 0314 0315 // vi:expandtab:tabstop=4 shiftwidth=4: 0316 0317 #include "moc_AutoStackImages.cpp"