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"