File indexing completed on 2024-05-05 04:19:14

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2007 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "fileoperations.h"
0023 
0024 // Qt
0025 #include <QFileDialog>
0026 #include <QMenu>
0027 #include <QPushButton>
0028 
0029 // KF
0030 #include "kio_version.h"
0031 #include <KIO/CopyJob>
0032 #include <KIO/DeleteJob>
0033 #include <KIO/DeleteOrTrashJob>
0034 #include <KIO/JobUiDelegate>
0035 #include <KIO/SimpleJob>
0036 #include <KJobWidgets>
0037 #include <KLocalizedString>
0038 
0039 // Local
0040 #include "gwenview_app_debug.h"
0041 #include "renamedialog.h"
0042 #include <lib/contextmanager.h>
0043 #include <lib/document/documentfactory.h>
0044 #include <lib/thumbnailprovider/thumbnailprovider.h>
0045 
0046 namespace Gwenview
0047 {
0048 namespace FileOperations
0049 {
0050 static void copyMoveOrLink(Operation operation, const QList<QUrl> &urlList, QWidget *parent, ContextManager *contextManager)
0051 {
0052     Q_ASSERT(!urlList.isEmpty());
0053     const int numberOfImages = urlList.count();
0054 
0055     auto dialog = new QFileDialog(parent->nativeParentWidget(), QString());
0056     dialog->setAcceptMode(QFileDialog::AcceptSave);
0057     dialog->setAttribute(Qt::WA_DeleteOnClose);
0058     dialog->setModal(true);
0059 
0060     // Figure out what the window title and buttons should say,
0061     // depending on the operation and how many images are selected
0062     switch (operation) {
0063     case COPY:
0064         if (numberOfImages == 1) {
0065             dialog->setWindowTitle(i18nc("@title:window %1 file name", "Copy %1", urlList.constFirst().fileName()));
0066         } else {
0067             dialog->setWindowTitle(i18ncp("@title:window %1 number of images", "Copy %1 image", "Copy %1 images", numberOfImages));
0068         }
0069         dialog->setLabelText(QFileDialog::DialogLabel::Accept, i18nc("@action:button", "Copy"));
0070         break;
0071     case MOVE:
0072         if (numberOfImages == 1) {
0073             dialog->setWindowTitle(i18nc("@title:window %1 file name", "Move %1", urlList.constFirst().fileName()));
0074         } else {
0075             dialog->setWindowTitle(i18ncp("@title:window %1 number of images", "Move %1 image", "Move %1 images", numberOfImages));
0076         }
0077         dialog->setLabelText(QFileDialog::DialogLabel::Accept, i18nc("@action:button", "Move"));
0078         break;
0079     case LINK:
0080         if (numberOfImages == 1) {
0081             dialog->setWindowTitle(i18nc("@title:window %1 file name", "Link %1", urlList.constFirst().fileName()));
0082         } else {
0083             dialog->setWindowTitle(i18ncp("@title:window %1 number of images", "Link %1 image", "Link %1 images", numberOfImages));
0084         }
0085         dialog->setLabelText(QFileDialog::DialogLabel::Accept, i18nc("@action:button", "Link"));
0086         break;
0087     default:
0088         Q_ASSERT(0);
0089     }
0090 
0091     if (numberOfImages == 1) {
0092         dialog->setFileMode(QFileDialog::AnyFile);
0093         dialog->selectUrl(urlList.constFirst());
0094     } else {
0095         dialog->setFileMode(QFileDialog::Directory);
0096         dialog->setOption(QFileDialog::ShowDirsOnly, true);
0097     }
0098 
0099     QUrl dirUrl = contextManager->targetDirUrl();
0100     if (!dirUrl.isValid()) {
0101         dirUrl = urlList.constFirst().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
0102     }
0103     dialog->setDirectoryUrl(dirUrl);
0104 
0105     QObject::connect(dialog, &QDialog::accepted, parent, [=]() {
0106         QUrl destUrl = dialog->selectedUrls().at(0);
0107 
0108         KIO::CopyJob *job = nullptr;
0109         switch (operation) {
0110         case COPY:
0111             job = KIO::copy(urlList, destUrl);
0112             break;
0113         case MOVE:
0114             job = KIO::move(urlList, destUrl);
0115             break;
0116         case LINK:
0117             job = KIO::link(urlList, destUrl);
0118             break;
0119         default:
0120             Q_ASSERT(0);
0121         }
0122         KJobWidgets::setWindow(job, parent);
0123         job->uiDelegate()->setAutoErrorHandlingEnabled(true);
0124 
0125         if (numberOfImages == 1) {
0126             destUrl = destUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
0127         }
0128         contextManager->setTargetDirUrl(destUrl);
0129     });
0130 
0131     dialog->show();
0132 }
0133 
0134 static void delOrTrash(KIO::JobUiDelegate::DeletionType deletionType, const QList<QUrl> &urlList, QWidget *parent)
0135 {
0136     Q_ASSERT(urlList.count() > 0);
0137 
0138     KJob *job = nullptr;
0139     switch (deletionType) {
0140     case KIO::JobUiDelegate::Trash:
0141         job = new KIO::DeleteOrTrashJob(urlList, KIO::AskUserActionInterface::Trash, KIO::AskUserActionInterface::DefaultConfirmation, parent);
0142         break;
0143     case KIO::JobUiDelegate::Delete:
0144         job = new KIO::DeleteOrTrashJob(urlList, KIO::AskUserActionInterface::Delete, KIO::AskUserActionInterface::DefaultConfirmation, parent);
0145         break;
0146     default: // e.g. EmptyTrash
0147         return;
0148     }
0149     Q_ASSERT(job);
0150     KJobWidgets::setWindow(job, parent);
0151     job->start();
0152 
0153     for (const QUrl &url : urlList) {
0154         DocumentFactory::instance()->forget(url);
0155     }
0156 }
0157 
0158 void copyTo(const QList<QUrl> &urlList, QWidget *parent, ContextManager *contextManager)
0159 {
0160     copyMoveOrLink(COPY, urlList, parent, contextManager);
0161 }
0162 
0163 void moveTo(const QList<QUrl> &urlList, QWidget *parent, ContextManager *contextManager)
0164 {
0165     copyMoveOrLink(MOVE, urlList, parent, contextManager);
0166 }
0167 
0168 void linkTo(const QList<QUrl> &urlList, QWidget *parent, ContextManager *contextManager)
0169 {
0170     copyMoveOrLink(LINK, urlList, parent, contextManager);
0171 }
0172 
0173 void trash(const QList<QUrl> &urlList, QWidget *parent)
0174 {
0175     delOrTrash(KIO::JobUiDelegate::Trash, urlList, parent);
0176 }
0177 
0178 void del(const QList<QUrl> &urlList, QWidget *parent)
0179 {
0180     delOrTrash(KIO::JobUiDelegate::Delete, urlList, parent);
0181 }
0182 
0183 void showMenuForDroppedUrls(QWidget *parent, const QList<QUrl> &urlList, const QUrl &destUrl)
0184 {
0185     if (urlList.isEmpty()) {
0186         qCWarning(GWENVIEW_APP_LOG) << "urlList is empty!";
0187         return;
0188     }
0189 
0190     if (!destUrl.isValid()) {
0191         qCWarning(GWENVIEW_APP_LOG) << "destUrl is not valid!";
0192         return;
0193     }
0194 
0195     QMenu menu(parent);
0196     QAction *moveAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-move"), QIcon::fromTheme(QStringLiteral("go-jump"))), i18n("Move Here"));
0197     QAction *copyAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Here"));
0198     QAction *linkAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-link")), i18n("Link Here"));
0199     menu.addSeparator();
0200     menu.addAction(QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Cancel"));
0201 
0202     QAction *action = menu.exec(QCursor::pos());
0203 
0204     KIO::Job *job = nullptr;
0205     if (action == moveAction) {
0206         job = KIO::move(urlList, destUrl);
0207     } else if (action == copyAction) {
0208         job = KIO::copy(urlList, destUrl);
0209     } else if (action == linkAction) {
0210         job = KIO::link(urlList, destUrl);
0211     } else {
0212         return;
0213     }
0214     Q_ASSERT(job);
0215     KJobWidgets::setWindow(job, parent);
0216 }
0217 
0218 void rename(const QUrl &oldUrl, QWidget *parent, ContextManager *contextManager)
0219 {
0220     auto dialog = new RenameDialog(parent);
0221     dialog->setFilename(oldUrl.fileName());
0222     dialog->setAttribute(Qt::WA_DeleteOnClose);
0223     dialog->setModal(true);
0224 
0225     QObject::connect(dialog, &QDialog::accepted, parent, [=]() {
0226         const QString name = dialog->filename();
0227         if (name.isEmpty() || name == oldUrl.fileName()) {
0228             return;
0229         }
0230 
0231         QUrl newUrl = oldUrl;
0232         newUrl = newUrl.adjusted(QUrl::RemoveFilename);
0233         newUrl.setPath(newUrl.path() + name);
0234         KIO::SimpleJob *job = KIO::rename(oldUrl, newUrl, KIO::HideProgressInfo);
0235         KJobWidgets::setWindow(job, parent);
0236         job->uiDelegate()->setAutoErrorHandlingEnabled(true);
0237         QObject::connect(job, &KJob::result, parent, [contextManager, job, oldUrl, newUrl]() {
0238             if (!job->error()) {
0239                 contextManager->setCurrentUrl(newUrl);
0240                 ThumbnailProvider::moveThumbnail(oldUrl, newUrl);
0241             }
0242         });
0243     });
0244 
0245     dialog->show();
0246 }
0247 
0248 } // namespace
0249 
0250 } // namespace