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

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, Boston, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "fileopscontextmanageritem.h"
0023 
0024 // Qt
0025 #include <QAction>
0026 #include <QApplication>
0027 #include <QClipboard>
0028 #include <QListView>
0029 #include <QMenu>
0030 #include <QShortcut>
0031 
0032 // KF
0033 #include <KActionCategory>
0034 #include <KActionCollection>
0035 #include <KFileItem>
0036 #include <KFileItemActions>
0037 #include <KFileItemListProperties>
0038 #include <KIO/ApplicationLauncherJob>
0039 #include <KIO/JobUiDelegate>
0040 #include <KIO/JobUiDelegateFactory>
0041 #include <KIO/OpenFileManagerWindowJob>
0042 #include <KIO/Paste>
0043 #include <KIO/PasteJob>
0044 #include <KIO/RestoreJob>
0045 #include <KJobWidgets>
0046 #include <KLocalizedString>
0047 #include <KOpenWithDialog>
0048 #include <KPropertiesDialog>
0049 #include <KUrlMimeData>
0050 #include <KXMLGUIClient>
0051 
0052 // Local
0053 #include "fileoperations.h"
0054 #include "sidebar.h"
0055 #include <lib/contextmanager.h>
0056 #include <lib/eventwatcher.h>
0057 #include <lib/gvdebug.h>
0058 #include <lib/mimetypeutils.h>
0059 
0060 namespace Gwenview
0061 {
0062 QList<QUrl> FileOpsContextManagerItem::urlList() const
0063 {
0064     return contextManager()->selectedFileItemList().urlList();
0065 }
0066 
0067 void FileOpsContextManagerItem::updateServiceList()
0068 {
0069     // This code is inspired from
0070     // kdebase/apps/lib/konq/konq_menuactions.cpp
0071 
0072     // Get list of all distinct mimetypes in selection
0073     QStringList mimeTypes;
0074     const auto selectedFileItemList = contextManager()->selectedFileItemList();
0075     for (const KFileItem &item : selectedFileItemList) {
0076         const QString mimeType = item.mimetype();
0077         if (!mimeTypes.contains(mimeType)) {
0078             mimeTypes << mimeType;
0079         }
0080     }
0081 
0082     mServiceList = KFileItemActions::associatedApplications(mimeTypes);
0083 }
0084 
0085 QMimeData *FileOpsContextManagerItem::selectionMimeData()
0086 {
0087     KFileItemList selectedFiles;
0088 
0089     // In Compare mode, restrict the returned mimedata to the focused image
0090     if (!mThumbnailView->isVisible()) {
0091         selectedFiles << KFileItem(contextManager()->currentUrl());
0092     } else {
0093         selectedFiles = contextManager()->selectedFileItemList();
0094     }
0095 
0096     return MimeTypeUtils::selectionMimeData(selectedFiles, MimeTypeUtils::ClipboardTarget);
0097 }
0098 
0099 QUrl FileOpsContextManagerItem::pasteTargetUrl() const
0100 {
0101     // If only one folder is selected, paste inside it, otherwise paste in
0102     // current
0103     const KFileItemList list = contextManager()->selectedFileItemList();
0104     if (list.count() == 1 && list.first().isDir()) {
0105         return list.first().url();
0106     } else {
0107         return contextManager()->currentDirUrl();
0108     }
0109 }
0110 
0111 static QAction *createSeparator(QObject *parent)
0112 {
0113     auto action = new QAction(parent);
0114     action->setSeparator(true);
0115     return action;
0116 }
0117 
0118 FileOpsContextManagerItem::FileOpsContextManagerItem(ContextManager *manager,
0119                                                      QListView *thumbnailView,
0120                                                      KActionCollection *actionCollection,
0121                                                      KXMLGUIClient *client)
0122     : AbstractContextManagerItem(manager)
0123 {
0124     mThumbnailView = thumbnailView;
0125     mXMLGUIClient = client;
0126     mGroup = new SideBarGroup(i18n("File Operations"));
0127     setWidget(mGroup);
0128     EventWatcher::install(mGroup, QEvent::Show, this, SLOT(updateSideBarContent()));
0129 
0130     mInTrash = false;
0131     mNewFileMenu = new KNewFileMenu(this);
0132 
0133     connect(contextManager(), &ContextManager::selectionChanged, this, &FileOpsContextManagerItem::updateActions);
0134     connect(contextManager(), &ContextManager::currentDirUrlChanged, this, &FileOpsContextManagerItem::updateActions);
0135 
0136     auto file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection);
0137     auto edit = new KActionCategory(i18nc("@title actions category", "Edit"), actionCollection);
0138 
0139     mCutAction = edit->addAction(KStandardAction::Cut, this, SLOT(cut()));
0140     mCopyAction = edit->addAction(KStandardAction::Copy, this, SLOT(copy()));
0141     mPasteAction = edit->addAction(KStandardAction::Paste, this, SLOT(paste()));
0142 
0143     mCopyToAction = file->addAction(QStringLiteral("file_copy_to"), this, SLOT(copyTo()));
0144     mCopyToAction->setText(i18nc("Verb", "Copy To..."));
0145     mCopyToAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
0146     actionCollection->setDefaultShortcut(mCopyToAction, Qt::Key_F7);
0147 
0148     mMoveToAction = file->addAction(QStringLiteral("file_move_to"), this, SLOT(moveTo()));
0149     mMoveToAction->setText(i18nc("Verb", "Move To..."));
0150     mMoveToAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-move"), QIcon::fromTheme(QStringLiteral("go-jump"))));
0151     actionCollection->setDefaultShortcut(mMoveToAction, Qt::Key_F8);
0152 
0153     mLinkToAction = file->addAction(QStringLiteral("file_link_to"), this, SLOT(linkTo()));
0154     mLinkToAction->setText(i18nc("Verb: create link to the file where user wants", "Link To..."));
0155     mLinkToAction->setIcon(QIcon::fromTheme(QStringLiteral("link")));
0156     actionCollection->setDefaultShortcut(mLinkToAction, Qt::Key_F9);
0157 
0158     mRenameAction = file->addAction(QStringLiteral("file_rename"), this, SLOT(rename()));
0159     mRenameAction->setText(i18nc("Verb", "Rename..."));
0160     mRenameAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
0161     actionCollection->setDefaultShortcut(mRenameAction, Qt::Key_F2);
0162 
0163     mTrashAction = file->addAction(QStringLiteral("file_trash"), this, SLOT(trash()));
0164     mTrashAction->setText(i18nc("Verb", "Trash"));
0165     mTrashAction->setIcon(QIcon::fromTheme(QStringLiteral("user-trash")));
0166     actionCollection->setDefaultShortcut(mTrashAction, Qt::Key_Delete);
0167 
0168     mDelAction = file->addAction(KStandardAction::DeleteFile, this, SLOT(del()));
0169 
0170     mRestoreAction = file->addAction(QStringLiteral("file_restore"), this, SLOT(restore()));
0171     mRestoreAction->setText(i18n("Restore"));
0172     mRestoreAction->setIcon(QIcon::fromTheme(QStringLiteral("restoration")));
0173 
0174     mShowPropertiesAction = file->addAction(QStringLiteral("file_show_properties"), this, SLOT(showProperties()));
0175     mShowPropertiesAction->setText(i18n("Properties"));
0176     mShowPropertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
0177     actionCollection->setDefaultShortcut(mShowPropertiesAction, QKeySequence(Qt::ALT | Qt::Key_Return));
0178 
0179     mCreateFolderAction = file->addAction(QStringLiteral("file_create_folder"), this, SLOT(createFolder()));
0180     mCreateFolderAction->setText(i18n("Create Folder..."));
0181     mCreateFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-new")));
0182 
0183     mOpenInNewWindowAction = file->addAction(QStringLiteral("file_open_in_new_window"));
0184     mOpenInNewWindowAction->setText(i18nc("@action:inmenu", "Open in New Window"));
0185     mOpenInNewWindowAction->setIcon(QIcon::fromTheme(QStringLiteral("window-new")));
0186     connect(mOpenInNewWindowAction, &QAction::triggered, this, &FileOpsContextManagerItem::openInNewWindow);
0187 
0188     mOpenWithAction = file->addAction(QStringLiteral("file_open_with"));
0189     mOpenWithAction->setText(i18n("Open With"));
0190     mOpenWithAction->setIcon(QIcon::fromTheme(QStringLiteral("system-run")));
0191     auto menu = new QMenu;
0192     mOpenWithAction->setMenu(menu);
0193     connect(menu, &QMenu::aboutToShow, this, &FileOpsContextManagerItem::populateOpenMenu);
0194     connect(menu, &QMenu::triggered, this, &FileOpsContextManagerItem::openWith);
0195 
0196     mOpenContainingFolderAction = file->addAction(QStringLiteral("file_open_containing_folder"), this, SLOT(openContainingFolder()));
0197     mOpenContainingFolderAction->setText(i18n("Open Containing Folder"));
0198     mOpenContainingFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder")));
0199 
0200     mRegularFileActionList << mRenameAction << mTrashAction << mDelAction << createSeparator(this) << mCopyToAction << mMoveToAction << mLinkToAction
0201                            << createSeparator(this) << mOpenInNewWindowAction << mOpenWithAction << mOpenContainingFolderAction << mShowPropertiesAction
0202                            << createSeparator(this) << mCreateFolderAction;
0203 
0204     mTrashFileActionList << mRestoreAction << mDelAction << createSeparator(this) << mShowPropertiesAction;
0205 
0206     connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &FileOpsContextManagerItem::updatePasteAction);
0207 
0208     updatePasteAction();
0209     // Delay action update because it must happen *after* main window has called
0210     // createGUI(), otherwise calling mXMLGUIClient->plugActionList() will
0211     // fail.
0212     QMetaObject::invokeMethod(this, &FileOpsContextManagerItem::updateActions, Qt::QueuedConnection);
0213 }
0214 
0215 FileOpsContextManagerItem::~FileOpsContextManagerItem()
0216 {
0217     delete mOpenWithAction->menu();
0218 }
0219 
0220 void FileOpsContextManagerItem::updateActions()
0221 {
0222     const int count = contextManager()->selectedFileItemList().count();
0223     const bool selectionNotEmpty = count > 0;
0224     const bool urlIsValid = contextManager()->currentUrl().isValid();
0225     const bool dirUrlIsValid = contextManager()->currentDirUrl().isValid();
0226 
0227     mInTrash = contextManager()->currentDirUrl().scheme() == QLatin1String("trash");
0228 
0229     mCutAction->setEnabled(selectionNotEmpty);
0230     mCopyAction->setEnabled(selectionNotEmpty);
0231     mCopyToAction->setEnabled(selectionNotEmpty);
0232     mMoveToAction->setEnabled(selectionNotEmpty);
0233     mLinkToAction->setEnabled(selectionNotEmpty);
0234     mTrashAction->setEnabled(selectionNotEmpty);
0235     mRestoreAction->setEnabled(selectionNotEmpty);
0236     mDelAction->setEnabled(selectionNotEmpty);
0237     mOpenInNewWindowAction->setEnabled(selectionNotEmpty);
0238     mOpenWithAction->setEnabled(selectionNotEmpty);
0239     mRenameAction->setEnabled(count == 1);
0240     mOpenContainingFolderAction->setEnabled(selectionNotEmpty);
0241 
0242     mCreateFolderAction->setEnabled(dirUrlIsValid);
0243     mShowPropertiesAction->setEnabled(dirUrlIsValid || urlIsValid);
0244 
0245     mXMLGUIClient->unplugActionList(QStringLiteral("file_action_list"));
0246     QList<QAction *> &list = mInTrash ? mTrashFileActionList : mRegularFileActionList;
0247     mXMLGUIClient->plugActionList(QStringLiteral("file_action_list"), list);
0248 
0249     updateSideBarContent();
0250 }
0251 
0252 void FileOpsContextManagerItem::updatePasteAction()
0253 {
0254     const QMimeData *mimeData = QApplication::clipboard()->mimeData();
0255     bool enable;
0256     KFileItem destItem(pasteTargetUrl());
0257     const QString text = KIO::pasteActionText(mimeData, &enable, destItem);
0258     mPasteAction->setEnabled(enable);
0259     mPasteAction->setText(text);
0260 }
0261 
0262 void FileOpsContextManagerItem::updateSideBarContent()
0263 {
0264     if (!mGroup->isVisible()) {
0265         return;
0266     }
0267 
0268     mGroup->clear();
0269 
0270     // Some actions we want to exist in a general sense so they're accessible
0271     // via the menu structure and with keyboard shortcuts, but we don't want
0272     // them to appear in the sidebar because they're too dangerous, little-used,
0273     // and/or contribute to the main window being too tall on short screens; see
0274     // https://bugs.kde.org/458987.
0275     const QList<QAction *> itemsToOmit = {mDelAction, mCreateFolderAction, mOpenInNewWindowAction};
0276 
0277     QList<QAction *> &list = mInTrash ? mTrashFileActionList : mRegularFileActionList;
0278     for (QAction *action : qAsConst(list)) {
0279         if (action->isEnabled() && !action->isSeparator() && !itemsToOmit.contains(action)) {
0280             mGroup->addAction(action);
0281         }
0282     }
0283 }
0284 
0285 void FileOpsContextManagerItem::showProperties()
0286 {
0287     const KFileItemList list = contextManager()->selectedFileItemList();
0288     if (!list.isEmpty()) {
0289         KPropertiesDialog::showDialog(list, mGroup);
0290     } else {
0291         const QUrl url = contextManager()->currentDirUrl();
0292         KPropertiesDialog::showDialog(url, mGroup);
0293     }
0294 }
0295 
0296 void FileOpsContextManagerItem::cut()
0297 {
0298     QMimeData *mimeData = selectionMimeData();
0299     KIO::setClipboardDataCut(mimeData, true);
0300     QApplication::clipboard()->setMimeData(mimeData);
0301 }
0302 
0303 void FileOpsContextManagerItem::copy()
0304 {
0305     QMimeData *mimeData = selectionMimeData();
0306     KIO::setClipboardDataCut(mimeData, false);
0307     QApplication::clipboard()->setMimeData(mimeData);
0308 }
0309 
0310 void FileOpsContextManagerItem::paste()
0311 {
0312     KIO::Job *job = KIO::paste(QApplication::clipboard()->mimeData(), pasteTargetUrl());
0313     KJobWidgets::setWindow(job, mGroup);
0314 }
0315 
0316 void FileOpsContextManagerItem::trash()
0317 {
0318     FileOperations::trash(urlList(), mGroup);
0319 }
0320 
0321 void FileOpsContextManagerItem::del()
0322 {
0323     FileOperations::del(urlList(), mGroup);
0324 }
0325 
0326 void FileOpsContextManagerItem::restore()
0327 {
0328     KIO::RestoreJob *job = KIO::restoreFromTrash(urlList());
0329     KJobWidgets::setWindow(job, mGroup);
0330     job->uiDelegate()->setAutoErrorHandlingEnabled(true);
0331 }
0332 
0333 void FileOpsContextManagerItem::copyTo()
0334 {
0335     FileOperations::copyTo(urlList(), widget(), contextManager());
0336 }
0337 
0338 void FileOpsContextManagerItem::moveTo()
0339 {
0340     FileOperations::moveTo(urlList(), widget(), contextManager());
0341 }
0342 
0343 void FileOpsContextManagerItem::linkTo()
0344 {
0345     FileOperations::linkTo(urlList(), widget(), contextManager());
0346 }
0347 
0348 void FileOpsContextManagerItem::rename()
0349 {
0350     if (mThumbnailView->isVisible()) {
0351         QModelIndex index = mThumbnailView->currentIndex();
0352         mThumbnailView->edit(index);
0353     } else {
0354         FileOperations::rename(urlList().constFirst(), mGroup, contextManager());
0355         contextManager()->slotSelectionChanged();
0356     }
0357 }
0358 
0359 void FileOpsContextManagerItem::createFolder()
0360 {
0361     const QUrl url = contextManager()->currentDirUrl();
0362     mNewFileMenu->setParentWidget(mGroup);
0363     mNewFileMenu->setWorkingDirectory(url);
0364     mNewFileMenu->createDirectory();
0365 }
0366 
0367 void FileOpsContextManagerItem::populateOpenMenu()
0368 {
0369     QMenu *openMenu = mOpenWithAction->menu();
0370     qDeleteAll(openMenu->actions());
0371 
0372     updateServiceList();
0373 
0374     int idx = -1;
0375     for (const KService::Ptr &service : qAsConst(mServiceList)) {
0376         ++idx;
0377         if (service->name() == QLatin1String("Gwenview")) {
0378             continue;
0379         }
0380         QString text = service->name().replace('&', "&&");
0381         QAction *action = openMenu->addAction(text);
0382         action->setIcon(QIcon::fromTheme(service->icon()));
0383         action->setData(idx);
0384     }
0385 
0386     openMenu->addSeparator();
0387     QAction *action = openMenu->addAction(QIcon::fromTheme(QStringLiteral("system-run")), i18n("Other Application..."));
0388     action->setData(-1);
0389 }
0390 
0391 void FileOpsContextManagerItem::openInNewWindow()
0392 {
0393     const QList<QUrl> urls = urlList();
0394     if (urls.isEmpty()) {
0395         return;
0396     }
0397 
0398     KService::Ptr service = KService::serviceByDesktopName(QStringLiteral("org.kde.gwenview"));
0399     if (!service) {
0400         return;
0401     }
0402 
0403     auto job = new KIO::ApplicationLauncherJob(service);
0404     job->setUrls(urls);
0405     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
0406     job->start();
0407 }
0408 
0409 void FileOpsContextManagerItem::openWith(QAction *action)
0410 {
0411     Q_ASSERT(action);
0412     KService::Ptr service;
0413     const QList<QUrl> list = urlList();
0414 
0415     bool ok;
0416     int idx = action->data().toInt(&ok);
0417     GV_RETURN_IF_FAIL(ok);
0418     if (idx != -1) {
0419         service = mServiceList.at(idx);
0420     }
0421     // If service is null, ApplicationLauncherJob will invoke the open-with dialog
0422     auto job = new KIO::ApplicationLauncherJob(service);
0423     job->setUrls(list);
0424     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, mGroup));
0425     job->start();
0426 }
0427 
0428 void FileOpsContextManagerItem::openContainingFolder()
0429 {
0430     KIO::highlightInFileManager(urlList());
0431 }
0432 
0433 } // namespace
0434 
0435 #include "moc_fileopscontextmanageritem.cpp"