// vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Self #include "fileopscontextmanageritem.h" // Qt #include #include #include #include #include #include // KF #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local #include "fileoperations.h" #include "sidebar.h" #include #include #include #include namespace Gwenview { QList FileOpsContextManagerItem::urlList() const { return contextManager()->selectedFileItemList().urlList(); } void FileOpsContextManagerItem::updateServiceList() { // This code is inspired from // kdebase/apps/lib/konq/konq_menuactions.cpp // Get list of all distinct mimetypes in selection QStringList mimeTypes; const auto selectedFileItemList = contextManager()->selectedFileItemList(); for (const KFileItem &item : selectedFileItemList) { const QString mimeType = item.mimetype(); if (!mimeTypes.contains(mimeType)) { mimeTypes << mimeType; } } mServiceList = KFileItemActions::associatedApplications(mimeTypes); } QMimeData *FileOpsContextManagerItem::selectionMimeData() { KFileItemList selectedFiles; // In Compare mode, restrict the returned mimedata to the focused image if (!mThumbnailView->isVisible()) { selectedFiles << KFileItem(contextManager()->currentUrl()); } else { selectedFiles = contextManager()->selectedFileItemList(); } return MimeTypeUtils::selectionMimeData(selectedFiles, MimeTypeUtils::ClipboardTarget); } QUrl FileOpsContextManagerItem::pasteTargetUrl() const { // If only one folder is selected, paste inside it, otherwise paste in // current const KFileItemList list = contextManager()->selectedFileItemList(); if (list.count() == 1 && list.first().isDir()) { return list.first().url(); } else { return contextManager()->currentDirUrl(); } } static QAction *createSeparator(QObject *parent) { auto action = new QAction(parent); action->setSeparator(true); return action; } FileOpsContextManagerItem::FileOpsContextManagerItem(ContextManager *manager, QListView *thumbnailView, KActionCollection *actionCollection, KXMLGUIClient *client) : AbstractContextManagerItem(manager) { mThumbnailView = thumbnailView; mXMLGUIClient = client; mGroup = new SideBarGroup(i18n("File Operations")); setWidget(mGroup); EventWatcher::install(mGroup, QEvent::Show, this, SLOT(updateSideBarContent())); mInTrash = false; mNewFileMenu = new KNewFileMenu(this); connect(contextManager(), &ContextManager::selectionChanged, this, &FileOpsContextManagerItem::updateActions); connect(contextManager(), &ContextManager::currentDirUrlChanged, this, &FileOpsContextManagerItem::updateActions); auto file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection); auto edit = new KActionCategory(i18nc("@title actions category", "Edit"), actionCollection); mCutAction = edit->addAction(KStandardAction::Cut, this, SLOT(cut())); mCopyAction = edit->addAction(KStandardAction::Copy, this, SLOT(copy())); mPasteAction = edit->addAction(KStandardAction::Paste, this, SLOT(paste())); mCopyToAction = file->addAction(QStringLiteral("file_copy_to"), this, SLOT(copyTo())); mCopyToAction->setText(i18nc("Verb", "Copy To...")); mCopyToAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); actionCollection->setDefaultShortcut(mCopyToAction, Qt::Key_F7); mMoveToAction = file->addAction(QStringLiteral("file_move_to"), this, SLOT(moveTo())); mMoveToAction->setText(i18nc("Verb", "Move To...")); mMoveToAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-move"), QIcon::fromTheme(QStringLiteral("go-jump")))); actionCollection->setDefaultShortcut(mMoveToAction, Qt::Key_F8); mLinkToAction = file->addAction(QStringLiteral("file_link_to"), this, SLOT(linkTo())); mLinkToAction->setText(i18nc("Verb: create link to the file where user wants", "Link To...")); mLinkToAction->setIcon(QIcon::fromTheme(QStringLiteral("link"))); actionCollection->setDefaultShortcut(mLinkToAction, Qt::Key_F9); mRenameAction = file->addAction(QStringLiteral("file_rename"), this, SLOT(rename())); mRenameAction->setText(i18nc("Verb", "Rename...")); mRenameAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); actionCollection->setDefaultShortcut(mRenameAction, Qt::Key_F2); mTrashAction = file->addAction(QStringLiteral("file_trash"), this, SLOT(trash())); mTrashAction->setText(i18nc("Verb", "Trash")); mTrashAction->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); actionCollection->setDefaultShortcut(mTrashAction, Qt::Key_Delete); mDelAction = file->addAction(KStandardAction::DeleteFile, this, SLOT(del())); mRestoreAction = file->addAction(QStringLiteral("file_restore"), this, SLOT(restore())); mRestoreAction->setText(i18n("Restore")); mRestoreAction->setIcon(QIcon::fromTheme(QStringLiteral("restoration"))); mShowPropertiesAction = file->addAction(QStringLiteral("file_show_properties"), this, SLOT(showProperties())); mShowPropertiesAction->setText(i18n("Properties")); mShowPropertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); actionCollection->setDefaultShortcut(mShowPropertiesAction, QKeySequence(Qt::ALT | Qt::Key_Return)); mCreateFolderAction = file->addAction(QStringLiteral("file_create_folder"), this, SLOT(createFolder())); mCreateFolderAction->setText(i18n("Create Folder...")); mCreateFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); mOpenInNewWindowAction = file->addAction(QStringLiteral("file_open_in_new_window")); mOpenInNewWindowAction->setText(i18nc("@action:inmenu", "Open in New Window")); mOpenInNewWindowAction->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); connect(mOpenInNewWindowAction, &QAction::triggered, this, &FileOpsContextManagerItem::openInNewWindow); mOpenWithAction = file->addAction(QStringLiteral("file_open_with")); mOpenWithAction->setText(i18n("Open With")); mOpenWithAction->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); auto menu = new QMenu; mOpenWithAction->setMenu(menu); connect(menu, &QMenu::aboutToShow, this, &FileOpsContextManagerItem::populateOpenMenu); connect(menu, &QMenu::triggered, this, &FileOpsContextManagerItem::openWith); mOpenContainingFolderAction = file->addAction(QStringLiteral("file_open_containing_folder"), this, SLOT(openContainingFolder())); mOpenContainingFolderAction->setText(i18n("Open Containing Folder")); mOpenContainingFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder"))); mRegularFileActionList << mRenameAction << mTrashAction << mDelAction << createSeparator(this) << mCopyToAction << mMoveToAction << mLinkToAction << createSeparator(this) << mOpenInNewWindowAction << mOpenWithAction << mOpenContainingFolderAction << mShowPropertiesAction << createSeparator(this) << mCreateFolderAction; mTrashFileActionList << mRestoreAction << mDelAction << createSeparator(this) << mShowPropertiesAction; connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &FileOpsContextManagerItem::updatePasteAction); updatePasteAction(); // Delay action update because it must happen *after* main window has called // createGUI(), otherwise calling mXMLGUIClient->plugActionList() will // fail. QMetaObject::invokeMethod(this, &FileOpsContextManagerItem::updateActions, Qt::QueuedConnection); } FileOpsContextManagerItem::~FileOpsContextManagerItem() { delete mOpenWithAction->menu(); } void FileOpsContextManagerItem::updateActions() { const int count = contextManager()->selectedFileItemList().count(); const bool selectionNotEmpty = count > 0; const bool urlIsValid = contextManager()->currentUrl().isValid(); const bool dirUrlIsValid = contextManager()->currentDirUrl().isValid(); mInTrash = contextManager()->currentDirUrl().scheme() == QLatin1String("trash"); mCutAction->setEnabled(selectionNotEmpty); mCopyAction->setEnabled(selectionNotEmpty); mCopyToAction->setEnabled(selectionNotEmpty); mMoveToAction->setEnabled(selectionNotEmpty); mLinkToAction->setEnabled(selectionNotEmpty); mTrashAction->setEnabled(selectionNotEmpty); mRestoreAction->setEnabled(selectionNotEmpty); mDelAction->setEnabled(selectionNotEmpty); mOpenInNewWindowAction->setEnabled(selectionNotEmpty); mOpenWithAction->setEnabled(selectionNotEmpty); mRenameAction->setEnabled(count == 1); mOpenContainingFolderAction->setEnabled(selectionNotEmpty); mCreateFolderAction->setEnabled(dirUrlIsValid); mShowPropertiesAction->setEnabled(dirUrlIsValid || urlIsValid); mXMLGUIClient->unplugActionList(QStringLiteral("file_action_list")); QList &list = mInTrash ? mTrashFileActionList : mRegularFileActionList; mXMLGUIClient->plugActionList(QStringLiteral("file_action_list"), list); updateSideBarContent(); } void FileOpsContextManagerItem::updatePasteAction() { const QMimeData *mimeData = QApplication::clipboard()->mimeData(); bool enable; KFileItem destItem(pasteTargetUrl()); const QString text = KIO::pasteActionText(mimeData, &enable, destItem); mPasteAction->setEnabled(enable); mPasteAction->setText(text); } void FileOpsContextManagerItem::updateSideBarContent() { if (!mGroup->isVisible()) { return; } mGroup->clear(); // Some actions we want to exist in a general sense so they're accessible // via the menu structure and with keyboard shortcuts, but we don't want // them to appear in the sidebar because they're too dangerous, little-used, // and/or contribute to the main window being too tall on short screens; see // https://bugs.kde.org/458987. const QList itemsToOmit = {mDelAction, mCreateFolderAction, mOpenInNewWindowAction}; QList &list = mInTrash ? mTrashFileActionList : mRegularFileActionList; for (QAction *action : qAsConst(list)) { if (action->isEnabled() && !action->isSeparator() && !itemsToOmit.contains(action)) { mGroup->addAction(action); } } } void FileOpsContextManagerItem::showProperties() { const KFileItemList list = contextManager()->selectedFileItemList(); if (!list.isEmpty()) { KPropertiesDialog::showDialog(list, mGroup); } else { const QUrl url = contextManager()->currentDirUrl(); KPropertiesDialog::showDialog(url, mGroup); } } void FileOpsContextManagerItem::cut() { QMimeData *mimeData = selectionMimeData(); KIO::setClipboardDataCut(mimeData, true); QApplication::clipboard()->setMimeData(mimeData); } void FileOpsContextManagerItem::copy() { QMimeData *mimeData = selectionMimeData(); KIO::setClipboardDataCut(mimeData, false); QApplication::clipboard()->setMimeData(mimeData); } void FileOpsContextManagerItem::paste() { KIO::Job *job = KIO::paste(QApplication::clipboard()->mimeData(), pasteTargetUrl()); KJobWidgets::setWindow(job, mGroup); } void FileOpsContextManagerItem::trash() { FileOperations::trash(urlList(), mGroup); } void FileOpsContextManagerItem::del() { FileOperations::del(urlList(), mGroup); } void FileOpsContextManagerItem::restore() { KIO::RestoreJob *job = KIO::restoreFromTrash(urlList()); KJobWidgets::setWindow(job, mGroup); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } void FileOpsContextManagerItem::copyTo() { FileOperations::copyTo(urlList(), widget(), contextManager()); } void FileOpsContextManagerItem::moveTo() { FileOperations::moveTo(urlList(), widget(), contextManager()); } void FileOpsContextManagerItem::linkTo() { FileOperations::linkTo(urlList(), widget(), contextManager()); } void FileOpsContextManagerItem::rename() { if (mThumbnailView->isVisible()) { QModelIndex index = mThumbnailView->currentIndex(); mThumbnailView->edit(index); } else { FileOperations::rename(urlList().constFirst(), mGroup, contextManager()); contextManager()->slotSelectionChanged(); } } void FileOpsContextManagerItem::createFolder() { const QUrl url = contextManager()->currentDirUrl(); mNewFileMenu->setParentWidget(mGroup); mNewFileMenu->setWorkingDirectory(url); mNewFileMenu->createDirectory(); } void FileOpsContextManagerItem::populateOpenMenu() { QMenu *openMenu = mOpenWithAction->menu(); qDeleteAll(openMenu->actions()); updateServiceList(); int idx = -1; for (const KService::Ptr &service : qAsConst(mServiceList)) { ++idx; if (service->name() == QLatin1String("Gwenview")) { continue; } QString text = service->name().replace('&', "&&"); QAction *action = openMenu->addAction(text); action->setIcon(QIcon::fromTheme(service->icon())); action->setData(idx); } openMenu->addSeparator(); QAction *action = openMenu->addAction(QIcon::fromTheme(QStringLiteral("system-run")), i18n("Other Application...")); action->setData(-1); } void FileOpsContextManagerItem::openInNewWindow() { const QList urls = urlList(); if (urls.isEmpty()) { return; } KService::Ptr service = KService::serviceByDesktopName(QStringLiteral("org.kde.gwenview")); if (!service) { return; } auto job = new KIO::ApplicationLauncherJob(service); job->setUrls(urls); job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr)); job->start(); } void FileOpsContextManagerItem::openWith(QAction *action) { Q_ASSERT(action); KService::Ptr service; const QList list = urlList(); bool ok; int idx = action->data().toInt(&ok); GV_RETURN_IF_FAIL(ok); if (idx != -1) { service = mServiceList.at(idx); } // If service is null, ApplicationLauncherJob will invoke the open-with dialog auto job = new KIO::ApplicationLauncherJob(service); job->setUrls(list); job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, mGroup)); job->start(); } void FileOpsContextManagerItem::openContainingFolder() { KIO::highlightInFileManager(urlList()); } } // namespace #include "moc_fileopscontextmanageritem.cpp"