436 lines
16 KiB
C++
436 lines
16 KiB
C++
|
// vim: set tabstop=4 shiftwidth=4 expandtab:
|
||
|
/*
|
||
|
Gwenview: an image viewer
|
||
|
Copyright 2007 Aurélien Gâteau <agateau@kde.org>
|
||
|
|
||
|
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 <QAction>
|
||
|
#include <QApplication>
|
||
|
#include <QClipboard>
|
||
|
#include <QListView>
|
||
|
#include <QMenu>
|
||
|
#include <QShortcut>
|
||
|
|
||
|
// KF
|
||
|
#include <KActionCategory>
|
||
|
#include <KActionCollection>
|
||
|
#include <KFileItem>
|
||
|
#include <KFileItemActions>
|
||
|
#include <KFileItemListProperties>
|
||
|
#include <KIO/ApplicationLauncherJob>
|
||
|
#include <KIO/JobUiDelegate>
|
||
|
#include <KIO/JobUiDelegateFactory>
|
||
|
#include <KIO/OpenFileManagerWindowJob>
|
||
|
#include <KIO/Paste>
|
||
|
#include <KIO/PasteJob>
|
||
|
#include <KIO/RestoreJob>
|
||
|
#include <KJobWidgets>
|
||
|
#include <KLocalizedString>
|
||
|
#include <KOpenWithDialog>
|
||
|
#include <KPropertiesDialog>
|
||
|
#include <KUrlMimeData>
|
||
|
#include <KXMLGUIClient>
|
||
|
|
||
|
// Local
|
||
|
#include "fileoperations.h"
|
||
|
#include "sidebar.h"
|
||
|
#include <lib/contextmanager.h>
|
||
|
#include <lib/eventwatcher.h>
|
||
|
#include <lib/gvdebug.h>
|
||
|
#include <lib/mimetypeutils.h>
|
||
|
|
||
|
namespace Gwenview
|
||
|
{
|
||
|
QList<QUrl> 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<QAction *> &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<QAction *> itemsToOmit = {mDelAction, mCreateFolderAction, mOpenInNewWindowAction};
|
||
|
|
||
|
QList<QAction *> &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<QUrl> 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<QUrl> 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"
|