File indexing completed on 2025-03-09 03:57:07
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2009-09-14 0007 * Description : a rename dialog for the AdvancedRename utility 0008 * 0009 * SPDX-FileCopyrightText: 2009-2012 by Andi Clemens <andi dot clemens at gmail dot com> 0010 * SPDX-FileCopyrightText: 2013-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "advancedrenamedialog.h" 0017 0018 // Qt includes 0019 0020 #include <QAction> 0021 #include <QActionGroup> 0022 #include <QEvent> 0023 #include <QFileInfo> 0024 #include <QGridLayout> 0025 #include <QMoveEvent> 0026 #include <QSet> 0027 #include <QString> 0028 #include <QTreeWidget> 0029 #include <QMenu> 0030 #include <QApplication> 0031 #include <QStyle> 0032 #include <QDialogButtonBox> 0033 #include <QVBoxLayout> 0034 #include <QPushButton> 0035 #include <QHeaderView> 0036 0037 // KDE includes 0038 0039 #include <klocalizedstring.h> 0040 #include <kconfiggroup.h> 0041 #include <ksharedconfig.h> 0042 0043 // Local includes 0044 0045 #include "digikam_globals.h" 0046 #include "advancedrenamewidget.h" 0047 #include "contextmenuhelper.h" 0048 #include "parser.h" 0049 #include "parsesettings.h" 0050 #include "advancedrenamemanager.h" 0051 #include "advancedrenameprocessdialog.h" 0052 #include "digikam_debug.h" 0053 0054 namespace Digikam 0055 { 0056 0057 class Q_DECL_HIDDEN AdvancedRenameListItem::Private 0058 { 0059 public: 0060 0061 explicit Private() 0062 { 0063 } 0064 0065 QUrl imageUrl; 0066 QString completeFileName; 0067 }; 0068 0069 // -------------------------------------------------------- 0070 0071 AdvancedRenameListItem::AdvancedRenameListItem(QTreeWidget* const view) 0072 : QTreeWidgetItem(view), 0073 d (new Private) 0074 { 0075 } 0076 0077 AdvancedRenameListItem::AdvancedRenameListItem(QTreeWidget* const view, const QUrl& url) 0078 : QTreeWidgetItem(view), 0079 d (new Private) 0080 { 0081 setImageUrl(url); 0082 } 0083 0084 AdvancedRenameListItem:: ~AdvancedRenameListItem() 0085 { 0086 delete d; 0087 } 0088 0089 void AdvancedRenameListItem::setImageUrl(const QUrl& url) 0090 { 0091 d->imageUrl = url; 0092 0093 QFileInfo fi(d->imageUrl.toLocalFile()); 0094 d->completeFileName = fi.fileName(); 0095 0096 setName(d->completeFileName); 0097 setNewName(d->completeFileName); 0098 } 0099 0100 QUrl AdvancedRenameListItem::imageUrl() const 0101 { 0102 return d->imageUrl; 0103 } 0104 0105 void AdvancedRenameListItem::setName(const QString& name) 0106 { 0107 setText(OldName, name); 0108 } 0109 0110 QString AdvancedRenameListItem::name() const 0111 { 0112 return text(OldName); 0113 } 0114 0115 void AdvancedRenameListItem::setNewName(const QString& name) 0116 { 0117 setText(NewName, name); 0118 } 0119 0120 QString AdvancedRenameListItem::newName() const 0121 { 0122 return text(NewName); 0123 } 0124 0125 void AdvancedRenameListItem::markInvalid(bool invalid) 0126 { 0127 QColor normalText = qApp->palette().text().color(); 0128 setForeground(OldName, invalid ? Qt::red : normalText); 0129 setForeground(NewName, invalid ? Qt::red : normalText); 0130 } 0131 0132 bool AdvancedRenameListItem::isNameEqual() const 0133 { 0134 return (name() == newName()); 0135 } 0136 0137 // -------------------------------------------------------- 0138 0139 class Q_DECL_HIDDEN AdvancedRenameDialog::Private 0140 { 0141 public: 0142 0143 explicit Private() 0144 : singleFileMode (false), 0145 minSizeDialog (450), 0146 sortActionName (nullptr), 0147 sortActionDate (nullptr), 0148 sortActionSize (nullptr), 0149 sortActionAscending (nullptr), 0150 sortActionDescending (nullptr), 0151 sortGroupActions (nullptr), 0152 sortGroupDirections (nullptr), 0153 listView (nullptr), 0154 buttons (nullptr), 0155 advancedRenameManager(nullptr), 0156 advancedRenameWidget (nullptr) 0157 { 0158 } 0159 0160 static const QString configGroupName; 0161 static const QString configLastUsedRenamePatternEntry; 0162 static const QString configDialogSizeEntry; 0163 0164 QString singleFileModeOldFilename; 0165 0166 bool singleFileMode; 0167 int minSizeDialog; 0168 0169 QAction* sortActionName; 0170 QAction* sortActionDate; 0171 QAction* sortActionSize; 0172 0173 QAction* sortActionAscending; 0174 QAction* sortActionDescending; 0175 0176 QActionGroup* sortGroupActions; 0177 QActionGroup* sortGroupDirections; 0178 0179 QTreeWidget* listView; 0180 QDialogButtonBox* buttons; 0181 0182 AdvancedRenameManager* advancedRenameManager; 0183 AdvancedRenameWidget* advancedRenameWidget; 0184 NewNamesList newNamesList; 0185 }; 0186 0187 const QString AdvancedRenameDialog::Private::configGroupName(QLatin1String("AdvancedRename Dialog")); 0188 const QString AdvancedRenameDialog::Private::configLastUsedRenamePatternEntry(QLatin1String("Last Used Rename Pattern")); 0189 const QString AdvancedRenameDialog::Private::configDialogSizeEntry(QLatin1String("Dialog Size")); 0190 0191 // -------------------------------------------------------- 0192 0193 AdvancedRenameDialog::AdvancedRenameDialog(QWidget* const parent) 0194 : QDialog(parent), 0195 d (new Private) 0196 { 0197 setWindowFlags((windowFlags() & ~Qt::Dialog) | 0198 Qt::Window | 0199 Qt::WindowCloseButtonHint | 0200 Qt::WindowMinMaxButtonsHint); 0201 0202 setModal(true); 0203 setupWidgets(); 0204 setupConnections(); 0205 0206 initDialog(); 0207 readSettings(); 0208 } 0209 0210 AdvancedRenameDialog::~AdvancedRenameDialog() 0211 { 0212 writeSettings(); 0213 delete d->advancedRenameManager; 0214 delete d; 0215 } 0216 0217 void AdvancedRenameDialog::setupWidgets() 0218 { 0219 d->buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); 0220 d->buttons->button(QDialogButtonBox::Ok)->setDefault(true); 0221 0222 d->advancedRenameManager = new AdvancedRenameManager(); 0223 d->advancedRenameWidget = new AdvancedRenameWidget(this); 0224 d->advancedRenameManager->setWidget(d->advancedRenameWidget); 0225 0226 // -------------------------------------------------------- 0227 0228 d->sortActionName = new QAction(i18n("By Name"), this); 0229 d->sortActionDate = new QAction(i18n("By Date"), this); 0230 d->sortActionSize = new QAction(i18n("By File Size"), this); 0231 0232 d->sortActionName->setCheckable(true); 0233 d->sortActionDate->setCheckable(true); 0234 d->sortActionSize->setCheckable(true); 0235 0236 // -------------------------------------------------------- 0237 0238 d->sortActionAscending = new QAction(i18nc("@action: sort type", "Ascending"), this); 0239 d->sortActionDescending = new QAction(i18nc("@action: sort type", "Descending"), this); 0240 0241 d->sortActionAscending->setCheckable(true); 0242 d->sortActionDescending->setCheckable(true); 0243 d->sortActionAscending->setChecked(true); 0244 0245 // -------------------------------------------------------- 0246 0247 d->sortGroupActions = new QActionGroup(this); 0248 d->sortGroupDirections = new QActionGroup(this); 0249 0250 d->sortGroupActions->addAction(d->sortActionName); 0251 d->sortGroupActions->addAction(d->sortActionDate); 0252 d->sortGroupActions->addAction(d->sortActionSize); 0253 0254 d->sortGroupDirections->addAction(d->sortActionAscending); 0255 d->sortGroupDirections->addAction(d->sortActionDescending); 0256 0257 // -------------------------------------------------------- 0258 0259 d->listView = new QTreeWidget(this); 0260 d->listView->setRootIsDecorated(false); 0261 d->listView->setSelectionMode(QAbstractItemView::NoSelection); 0262 d->listView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 0263 d->listView->setAllColumnsShowFocus(true); 0264 d->listView->setSortingEnabled(false); 0265 d->listView->setColumnCount(2); 0266 d->listView->setHeaderLabels(QStringList() << i18n("Current Name") << i18n("New Name")); 0267 d->listView->setContextMenuPolicy(Qt::CustomContextMenu); 0268 d->listView->header()->setSectionResizeMode(0, QHeaderView::Stretch); 0269 d->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch); 0270 d->listView->setWhatsThis(i18n("This list shows the results for your renaming pattern. Red items indicate a " 0271 "name collision, either because the new name is equal to the current name, " 0272 "or because the name has already been assigned to another item.")); 0273 0274 // -------------------------------------------------------- 0275 0276 QWidget* const mainWidget = new QWidget(this); 0277 QGridLayout* const mainLayout = new QGridLayout; 0278 mainLayout->addWidget(d->listView, 0, 0, 1, 1); 0279 mainLayout->addWidget(d->advancedRenameWidget, 1, 0, 1, 1); 0280 mainLayout->setRowStretch(0, 10); 0281 mainWidget->setLayout(mainLayout); 0282 0283 QVBoxLayout* const vbx = new QVBoxLayout(this); 0284 vbx->addWidget(mainWidget); 0285 vbx->addWidget(d->buttons); 0286 setLayout(vbx); 0287 0288 setMinimumWidth(d->advancedRenameWidget->minimumWidth()); 0289 } 0290 0291 void AdvancedRenameDialog::setupConnections() 0292 { 0293 connect(d->advancedRenameWidget, SIGNAL(signalTextChanged(QString)), 0294 this, SLOT(slotParseStringChanged(QString))); 0295 0296 connect(d->advancedRenameWidget, SIGNAL(signalReturnPressed()), 0297 this, SLOT(slotReturnPressed())); 0298 0299 connect(d->advancedRenameManager, SIGNAL(signalSortingChanged(QList<QUrl>)), 0300 this, SLOT(slotAddImages(QList<QUrl>))); 0301 0302 connect(d->listView, SIGNAL(customContextMenuRequested(QPoint)), 0303 this, SLOT(slotShowContextMenu(QPoint))); 0304 0305 connect(d->sortGroupActions, SIGNAL(triggered(QAction*)), 0306 this, SLOT(slotSortActionTriggered(QAction*))); 0307 0308 connect(d->sortGroupDirections, SIGNAL(triggered(QAction*)), 0309 this, SLOT(slotSortDirectionTriggered(QAction*))); 0310 0311 connect(d->buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), 0312 this, SLOT(accept())); 0313 0314 connect(d->buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), 0315 this, SLOT(reject())); 0316 0317 connect(d->buttons->button(QDialogButtonBox::Help), SIGNAL(clicked()), 0318 this, SLOT(slotHelp())); 0319 } 0320 0321 void AdvancedRenameDialog::initDialog() 0322 { 0323 QStringList fileList = d->advancedRenameManager->fileList(); 0324 int count = fileList.size(); 0325 0326 QString title = i18np("Rename", "Rename (%1 images)", count); 0327 setWindowTitle(title); 0328 0329 if (count < 1) 0330 { 0331 d->listView->clear(); 0332 return; 0333 } 0334 0335 d->singleFileMode = (count == 1); 0336 0337 Q_FOREACH (const QString& file, fileList) 0338 { 0339 QUrl url = QUrl::fromLocalFile(file); 0340 new AdvancedRenameListItem(d->listView, url); 0341 } 0342 0343 // set current filename if only one image has been added 0344 0345 if (d->singleFileMode) 0346 { 0347 QFileInfo info(fileList.first()); 0348 d->advancedRenameWidget->setParseString(info.completeBaseName()); 0349 d->advancedRenameWidget->setParseTimerDuration(50); 0350 d->advancedRenameWidget->focusLineEdit(); 0351 d->advancedRenameWidget->highlightLineEdit(); 0352 d->singleFileModeOldFilename = info.fileName(); 0353 } 0354 0355 d->buttons->button(QDialogButtonBox::Ok)->setEnabled(checkNewNames()); 0356 } 0357 0358 void AdvancedRenameDialog::readSettings() 0359 { 0360 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0361 KConfigGroup group = config->group(d->configGroupName); 0362 0363 QSize s = group.readEntry(d->configDialogSizeEntry, QSize(d->minSizeDialog, d->minSizeDialog)); 0364 resize(s); 0365 d->advancedRenameWidget->setParseString(group.readEntry(d->configLastUsedRenamePatternEntry, QString())); 0366 } 0367 0368 void AdvancedRenameDialog::writeSettings() 0369 { 0370 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0371 KConfigGroup group = config->group(d->configGroupName); 0372 0373 group.writeEntry(d->configDialogSizeEntry, size()); 0374 0375 if (d->singleFileMode) 0376 { 0377 d->advancedRenameWidget->clear(); 0378 } 0379 else 0380 { 0381 group.writeEntry(d->configLastUsedRenamePatternEntry, d->advancedRenameWidget->parseString()); 0382 } 0383 } 0384 0385 void AdvancedRenameDialog::slotReturnPressed() 0386 { 0387 if (d->buttons->button(QDialogButtonBox::Ok)->isEnabled()) 0388 { 0389 accept(); 0390 } 0391 } 0392 0393 void AdvancedRenameDialog::slotSortActionTriggered(QAction* action) 0394 { 0395 if (!action) 0396 { 0397 d->advancedRenameManager->setSortAction(AdvancedRenameManager::SortCustom); 0398 } 0399 else if (action == d->sortActionName) 0400 { 0401 d->advancedRenameManager->setSortAction(AdvancedRenameManager::SortName); 0402 } 0403 else if (action == d->sortActionDate) 0404 { 0405 d->advancedRenameManager->setSortAction(AdvancedRenameManager::SortDate); 0406 } 0407 else if (action == d->sortActionSize) 0408 { 0409 d->advancedRenameManager->setSortAction(AdvancedRenameManager::SortSize); 0410 } 0411 } 0412 0413 void AdvancedRenameDialog::slotSortDirectionTriggered(QAction* direction) 0414 { 0415 if (direction == d->sortActionAscending) 0416 { 0417 d->advancedRenameManager->setSortDirection(AdvancedRenameManager::SortAscending); 0418 } 0419 else if (direction == d->sortActionDescending) 0420 { 0421 d->advancedRenameManager->setSortDirection(AdvancedRenameManager::SortDescending); 0422 } 0423 } 0424 0425 void AdvancedRenameDialog::slotShowContextMenu(const QPoint& pos) 0426 { 0427 QMenu menu(this); 0428 menu.addSection(i18n("Sort Images")); 0429 0430 ContextMenuHelper cmhelper(&menu); 0431 cmhelper.addAction(d->sortActionName); 0432 cmhelper.addAction(d->sortActionDate); 0433 cmhelper.addAction(d->sortActionSize); 0434 cmhelper.addSeparator(); 0435 cmhelper.addAction(d->sortActionAscending); 0436 cmhelper.addAction(d->sortActionDescending); 0437 0438 cmhelper.exec(d->listView->viewport()->mapToGlobal(pos)); 0439 } 0440 0441 void AdvancedRenameDialog::slotParseStringChanged(const QString& parseString) 0442 { 0443 if (!d->advancedRenameManager) 0444 { 0445 return; 0446 } 0447 0448 if (!d->singleFileMode) 0449 { 0450 QApplication::setOverrideCursor(Qt::WaitCursor); 0451 } 0452 0453 d->newNamesList.clear(); 0454 0455 // generate new file names 0456 0457 ParseSettings settings; 0458 settings.useOriginalFileExtension = true; 0459 0460 // settings.useOriginalFileExtension = d->singleFileMode ? false : true; 0461 0462 d->advancedRenameManager->parseFiles(parseString, settings); 0463 0464 // fill the tree widget with the updated files 0465 0466 QTreeWidgetItemIterator it(d->listView); 0467 0468 while (*it) 0469 { 0470 AdvancedRenameListItem* const item = dynamic_cast<AdvancedRenameListItem*>((*it)); 0471 0472 if (item) 0473 { 0474 QString newName = d->advancedRenameManager->newName(item->imageUrl().toLocalFile()); 0475 item->setNewName(newName); 0476 d->newNamesList << NewNameInfo(item->imageUrl(), newName); 0477 } 0478 0479 ++it; 0480 } 0481 0482 bool enableBtn = checkNewNames() && !parseString.isEmpty(); 0483 d->buttons->button(QDialogButtonBox::Ok)->setEnabled(enableBtn); 0484 0485 d->listView->viewport()->update(); 0486 0487 if (!d->singleFileMode) 0488 { 0489 QApplication::restoreOverrideCursor(); 0490 } 0491 } 0492 0493 void AdvancedRenameDialog::slotAddImages(const QList<QUrl>& urls) 0494 { 0495 if (urls.isEmpty()) 0496 { 0497 qCDebug(DIGIKAM_GENERAL_LOG) << "No item to process"; 0498 return; 0499 } 0500 0501 d->listView->clear(); 0502 d->advancedRenameManager->reset(); 0503 QList<ParseSettings> files; 0504 0505 Q_FOREACH (const QUrl& url, urls) 0506 { 0507 ParseSettings ps; 0508 ps.fileUrl = url; 0509 files << ps; 0510 qCDebug(DIGIKAM_GENERAL_LOG) << url; 0511 } 0512 0513 d->advancedRenameManager->addFiles(files); 0514 0515 initDialog(); 0516 slotParseStringChanged(d->advancedRenameWidget->parseString()); 0517 } 0518 0519 NewNamesList AdvancedRenameDialog::newNames() const 0520 { 0521 return filterNewNames(); 0522 } 0523 0524 bool AdvancedRenameDialog::checkNewNames() const 0525 { 0526 int numNames = 0; 0527 int numEqualNames = 0; 0528 bool ok = true; 0529 0530 QSet<QString> tmpNewNames; 0531 QTreeWidgetItemIterator it(d->listView); 0532 0533 while (*it) 0534 { 0535 AdvancedRenameListItem* const item = dynamic_cast<AdvancedRenameListItem*>((*it)); 0536 0537 if (item) 0538 { 0539 ++numNames; 0540 QFileInfo fi(item->imageUrl().toLocalFile()); 0541 0542 QString completeNewName = fi.path(); 0543 completeNewName.append(QLatin1Char('/')); 0544 completeNewName.append(item->newName()); 0545 0546 bool invalid = tmpNewNames.contains(completeNewName); 0547 tmpNewNames << completeNewName; 0548 0549 item->markInvalid(invalid); 0550 ok &= !invalid; 0551 0552 if (item->isNameEqual()) 0553 { 0554 ++ numEqualNames; 0555 } 0556 } 0557 0558 ++it; 0559 } 0560 0561 return (ok && !(numNames == numEqualNames)); 0562 } 0563 0564 NewNamesList AdvancedRenameDialog::filterNewNames() const 0565 { 0566 NewNamesList filteredNames; 0567 QTreeWidgetItemIterator it(d->listView); 0568 0569 while (*it) 0570 { 0571 AdvancedRenameListItem* const item = dynamic_cast<AdvancedRenameListItem*>((*it)); 0572 0573 if (item && !item->isNameEqual()) 0574 { 0575 filteredNames << NewNameInfo(item->imageUrl(), item->newName()); 0576 } 0577 0578 ++it; 0579 } 0580 0581 return filteredNames; 0582 } 0583 0584 void AdvancedRenameDialog::slotHelp() 0585 { 0586 openOnlineDocumentation(QLatin1String("main_window"), QLatin1String("image_view"), QLatin1String("renaming-photograph")); 0587 } 0588 0589 } // namespace Digikam 0590 0591 #include "moc_advancedrenamedialog.cpp"