// 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/OpenFileManagerWindowJob>
#include <KIO/Paste>
#include <KIO/PasteJob>
#include <KIO/RestoreJob>
#include <KJobWidgets>
#include <KLocalizedString>
#include <KOpenWithDialog>
#include <KPropertiesDialog>
#include <KUrlMimeData>
#include <KXMLGUIClient>
#include <kio_version.h>
#if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
#include <KIO/JobUiDelegateFactory>
#else
#include <KIO/JobUiDelegate>
#endif

// 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);
#if KIO_VERSION >= QT_VERSION_CHECK(5, 97, 0)
    mNewFileMenu->setWorkingDirectory(url);
#else
    mNewFileMenu->setPopupFiles(QList<QUrl>() << url);
#endif
    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);
#if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
    job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
#else
    job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
#endif
    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);
#if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
    job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, mGroup));
#else
    job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, mGroup));
#endif
    job->start();
}

void FileOpsContextManagerItem::openContainingFolder()
{
    KIO::highlightInFileManager(urlList());
}

} // namespace
