Blob Blame History Raw
/*
    Copyright (c) 2017, Lukas Holecek <hluk@email.cz>

    This file is part of CopyQ.

    CopyQ 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 3 of the License, or
    (at your option) any later version.

    CopyQ 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 CopyQ.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "actionhandler.h"

#include "common/appconfig.h"
#include "common/action.h"
#include "common/common.h"
#include "common/contenttype.h"
#include "common/display.h"
#include "common/log.h"
#include "common/mimetypes.h"
#include "common/textdata.h"
#include "gui/actiondialog.h"
#include "gui/processmanagerdialog.h"
#include "gui/clipboardbrowser.h"
#include "gui/mainwindow.h"
#include "item/serialize.h"

#include <QDateTime>
#include <QModelIndex>

#include <cmath>

ActionHandler::ActionHandler(MainWindow *mainWindow)
    : QObject(mainWindow)
    , m_wnd(mainWindow)
    , m_actionCounter(0)
    , m_activeActionDialog(new ProcessManagerDialog(mainWindow))
{
    Q_ASSERT(mainWindow);
}

ActionDialog *ActionHandler::createActionDialog(const QStringList &tabs)
{
    auto actionDialog = new ActionDialog(m_wnd);
    actionDialog->setAttribute(Qt::WA_DeleteOnClose, true);
    actionDialog->setOutputTabs(tabs, QString());
    actionDialog->setCommand(m_lastActionDialogCommand);

    connect( actionDialog, SIGNAL(accepted(Action*)),
             this, SLOT(action(Action*)) );
    connect( actionDialog, SIGNAL(closed(ActionDialog*)),
             this, SLOT(actionDialogClosed(ActionDialog*)) );

    return actionDialog;
}

bool ActionHandler::hasRunningAction() const
{
    return m_actionCounter > 0;
}

void ActionHandler::showProcessManagerDialog()
{
    m_activeActionDialog->show();
}

void ActionHandler::addFinishedAction(const QString &name)
{
    m_activeActionDialog->actionFinished(name);
}

QVariantMap ActionHandler::actionData(int id) const
{
    const auto action = m_actions.value(id);
    return action ? action->data() : QVariantMap();
}

void ActionHandler::setActionData(int id, const QVariantMap &data)
{
    const auto action = m_actions.value(id);
    if (action)
        action->setData(data);
}

void ActionHandler::action(Action *action)
{
    action->setParent(this);

    const auto id = ++m_lastActionId;
    action->setId(id);
    m_actions.insert(id, action);

    m_lastAction = action;

    connect( action, SIGNAL(newItems(QStringList, QString, QString)),
             this, SLOT(addItems(QStringList, QString, QString)) );
    connect( action, SIGNAL(newItem(QByteArray, QString, QString)),
             this, SLOT(addItem(QByteArray, QString, QString)) );
    connect( action, SIGNAL(changeItem(QByteArray, QString, QModelIndex)),
             this, SLOT(changeItem(QByteArray, QString, QModelIndex)) );
    connect( action, SIGNAL(actionStarted(Action*)),
             this, SLOT(actionStarted(Action*)) );
    connect( action, SIGNAL(actionFinished(Action*)),
             this, SLOT(closeAction(Action*)) );
    connect( action, SIGNAL(actionError(Action*)),
             this, SLOT(closeAction(Action*)) );

    ++m_actionCounter;

    if ( !action->outputFormat().isEmpty() && action->outputTab().isEmpty() )
        action->setOutputTab(m_currentTabName);

    m_activeActionDialog->actionAboutToStart(action);
    COPYQ_LOG( QString("Executing: %1").arg(action->command()) );
    action->start();
}

void ActionHandler::actionStarted(Action *action)
{
    m_activeActionDialog->actionStarted(action);
    emit runningActionsCountChanged();
}

void ActionHandler::closeAction(Action *action)
{
    m_actions.remove(action->id());

    QString msg;

    QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::Information;

    if ( action->actionFailed() ) {
        msg = tr("Error: %1\n").arg(action->errorString()) + action->errorOutput();
        icon = QSystemTrayIcon::Critical;
    } else if ( action->exitCode() != 0 ) {
        if ( !action->ignoreExitCode() ) {
            msg = tr("Exit code: %1\n").arg(action->exitCode()) + "\n" + action->errorOutput();
            icon = QSystemTrayIcon::Warning;
        }
    } else if ( !action->inputFormats().isEmpty() ) {
        const QModelIndex index = action->index();
        ClipboardBrowser *c = m_wnd->browserForItem(index);
        if (c) {
            QStringList removeFormats = action->inputFormats();
            removeFormats.removeAll( action->outputFormat() );

            if ( !removeFormats.isEmpty() )
                c->model()->setData(index, removeFormats, contentType::removeFormats);
        }
    }

    if ( !msg.isEmpty() ) {
        const int maxWidthPoints =
                AppConfig().option<Config::notification_maximum_width>();
        const QString command = action->command()
                .replace("copyq eval --", "copyq:");
        const QString name = action->name().isEmpty()
                ? QString(command).replace('\n', " ")
                : action->name();
        const QString format = tr("Command %1").arg(quoteString("%1"));
        const QString title = elideText(name, QFont(), format, pointsToPixels(maxWidthPoints));

        // Print command with line numbers.
        int lineNumber = 0;
        const auto lines = command.split("\n");
        const auto lineNumberWidth = std::log10(lines.size()) + 1;
        for (const auto &line : lines)
            msg.append(QString("\n%1. %2").arg(++lineNumber, lineNumberWidth).arg(line));

        log(title + "\n" + msg);
        m_wnd->showMessage(title, msg, icon);
    }

    m_activeActionDialog->actionFinished(action);
    Q_ASSERT(m_actionCounter > 0);
    --m_actionCounter;

    emit runningActionsCountChanged();

    action->deleteLater();
}

void ActionHandler::actionDialogClosed(ActionDialog *dialog)
{
    m_lastActionDialogCommand = dialog->command();
}

void ActionHandler::addItems(const QStringList &items, const QString &format, const QString &tabName)
{
    ClipboardBrowser *c = tabName.isEmpty() ? m_wnd->browser() : m_wnd->tab(tabName);
    for (const auto &item : items)
        c->add( createDataMap(format, item) );

    if (m_lastAction) {
        if (m_lastAction == sender())
            c->setCurrent(items.size() - 1);
        m_lastAction = nullptr;
    }
}

void ActionHandler::addItem(const QByteArray &data, const QString &format, const QString &tabName)
{
    ClipboardBrowser *c = tabName.isEmpty() ? m_wnd->browser() : m_wnd->tab(tabName);
    c->add( createDataMap(format, data) );

    if (m_lastAction) {
        if (m_lastAction == sender())
            c->setCurrent(0);
        m_lastAction = nullptr;
    }
}

void ActionHandler::changeItem(const QByteArray &data, const QString &format, const QModelIndex &index)
{
    ClipboardBrowser *c = m_wnd->browserForItem(index);
    if (c == nullptr)
        return;

    QVariantMap dataMap;
    if (format == mimeItems)
        deserializeData(&dataMap, data);
    else
        dataMap.insert(format, data);
    c->model()->setData(index, dataMap, contentType::updateData);
}