4c75f9e
/*
4c75f9e
    Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
4c75f9e
4c75f9e
    This file is part of CopyQ.
4c75f9e
4c75f9e
    CopyQ is free software: you can redistribute it and/or modify
4c75f9e
    it under the terms of the GNU General Public License as published by
4c75f9e
    the Free Software Foundation, either version 3 of the License, or
4c75f9e
    (at your option) any later version.
4c75f9e
4c75f9e
    CopyQ is distributed in the hope that it will be useful,
4c75f9e
    but WITHOUT ANY WARRANTY; without even the implied warranty of
4c75f9e
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
4c75f9e
    GNU General Public License for more details.
4c75f9e
4c75f9e
    You should have received a copy of the GNU General Public License
4c75f9e
    along with CopyQ.  If not, see <http://www.gnu.org/licenses/>.
4c75f9e
*/
4c75f9e
4c75f9e
#include "itemdata.h"
4c75f9e
#include "ui_itemdatasettings.h"
4c75f9e
4c75f9e
#include "common/contenttype.h"
4c75f9e
#include "common/mimetypes.h"
4c75f9e
#include "common/textdata.h"
4c75f9e
4c75f9e
#include <QContextMenuEvent>
4c75f9e
#include <QModelIndex>
4c75f9e
#include <QMouseEvent>
4c75f9e
#include <QTextCodec>
4c75f9e
#include <QtPlugin>
4c75f9e
4c75f9e
namespace {
4c75f9e
4c75f9e
// Limit number of characters for performance reasons.
4c75f9e
const int defaultMaxBytes = 256;
4c75f9e
4c75f9e
QString hexData(const QByteArray &data)
4c75f9e
{
4c75f9e
    if ( data.isEmpty() )
4c75f9e
        return QString();
4c75f9e
4c75f9e
    QString result;
4c75f9e
    QString chars;
4c75f9e
4c75f9e
    int i = 0;
4c75f9e
    forever {
4c75f9e
        if (i > 0) {
4c75f9e
            if ( (i % 2) == 0 )
4c75f9e
                result.append( QString(" ") );
4c75f9e
            if ( (i % 16) == 0 ) {
4c75f9e
                result.append(" ");
4c75f9e
                result.append(chars);
4c75f9e
                result.append( QString("\n") );
4c75f9e
                chars.clear();
4c75f9e
                if (i >= data.size() )
4c75f9e
                    break;
4c75f9e
            }
4c75f9e
        }
4c75f9e
        if ( (i % 16) == 0 ) {
4c75f9e
            result.append( QString("%1: ").arg(QString::number(i, 16), 4, QChar('0')) );
4c75f9e
        }
4c75f9e
        if (i < data.size() ) {
4c75f9e
            QChar c = data[i];
4c75f9e
            result.append( QString("%1").arg(QString::number(c.unicode(), 16), 2, QChar('0')) );
4c75f9e
            chars.append( c.isPrint() ? escapeHtml(QString(c)) : QString(".") );
4c75f9e
        } else {
4c75f9e
            result.append( QString("  ") );
4c75f9e
        }
4c75f9e
4c75f9e
        ++i;
4c75f9e
    }
4c75f9e
4c75f9e
    return result;
4c75f9e
}
4c75f9e
4c75f9e
QString stringFromBytes(const QByteArray &bytes, const QString &format)
4c75f9e
{
4c75f9e
    QTextCodec *codec = QTextCodec::codecForName("utf-8");
4c75f9e
    if (format == QLatin1String("text/html"))
4c75f9e
        codec = QTextCodec::codecForHtml(bytes, codec);
4c75f9e
    return codec->toUnicode(bytes);
4c75f9e
}
4c75f9e
4c75f9e
bool emptyIntersection(const QStringList &lhs, const QStringList &rhs)
4c75f9e
{
4c75f9e
    for (const auto &l : lhs) {
4c75f9e
        if ( rhs.contains(l) )
4c75f9e
            return false;
4c75f9e
    }
4c75f9e
4c75f9e
    return true;
4c75f9e
}
4c75f9e
4c75f9e
} // namespace
4c75f9e
4c75f9e
ItemData::ItemData(const QModelIndex &index, int maxBytes, QWidget *parent)
4c75f9e
    : QLabel(parent)
4c75f9e
    , ItemWidget(this)
4c75f9e
{
4c75f9e
    setTextInteractionFlags(Qt::TextSelectableByMouse);
4c75f9e
    setContentsMargins(4, 4, 4, 4);
4c75f9e
    setTextFormat(Qt::RichText);
4c75f9e
4c75f9e
    QString text;
4c75f9e
4c75f9e
    const QVariantMap data = index.data(contentType::data).toMap();
4c75f9e
    for ( const auto &format : data.keys() ) {
4c75f9e
        QByteArray bytes = data[format].toByteArray();
4c75f9e
        const int size = bytes.size();
4c75f9e
        bool trimmed = size > maxBytes;
4c75f9e
        if (trimmed)
4c75f9e
            bytes = bytes.left(maxBytes);
4c75f9e
4c75f9e
        bool hasText = format.startsWith("text/") ||
4c75f9e
                       format.startsWith("application/x-copyq-owner-window-title");
4c75f9e
        const QString content = hasText ? escapeHtml(stringFromBytes(bytes, format)) : hexData(bytes);
4c75f9e
        text.append( QString("

") );

4c75f9e
        text.append( QString("%1 (%2 bytes)
%3
")
4c75f9e
                     .arg(format)
4c75f9e
                     .arg(size)
4c75f9e
                     .arg(content) );
4c75f9e
        text.append( QString("

") );
4c75f9e
4c75f9e
        if (trimmed)
4c75f9e
            text.append( QString("

...

") );
4c75f9e
    }
4c75f9e
4c75f9e
    setText(text);
4c75f9e
}
4c75f9e
4c75f9e
void ItemData::highlight(const QRegExp &, const QFont &, const QPalette &)
4c75f9e
{
4c75f9e
}
4c75f9e
4c75f9e
void ItemData::mousePressEvent(QMouseEvent *e)
4c75f9e
{
4c75f9e
    QLabel::mousePressEvent(e);
4c75f9e
    e->ignore();
4c75f9e
}
4c75f9e
4c75f9e
void ItemData::mouseDoubleClickEvent(QMouseEvent *e)
4c75f9e
{
4c75f9e
    if ( e->modifiers().testFlag(Qt::ShiftModifier) )
4c75f9e
        QLabel::mouseDoubleClickEvent(e);
4c75f9e
    else
4c75f9e
        e->ignore();
4c75f9e
}
4c75f9e
4c75f9e
void ItemData::contextMenuEvent(QContextMenuEvent *e)
4c75f9e
{
4c75f9e
    e->ignore();
4c75f9e
}
4c75f9e
4c75f9e
ItemDataLoader::ItemDataLoader()
4c75f9e
{
4c75f9e
}
4c75f9e
4c75f9e
ItemDataLoader::~ItemDataLoader() = default;
4c75f9e
4c75f9e
ItemWidget *ItemDataLoader::create(const QModelIndex &index, QWidget *parent, bool preview) const
4c75f9e
{
4c75f9e
    if ( index.data(contentType::isHidden).toBool() )
4c75f9e
        return nullptr;
4c75f9e
4c75f9e
    const QStringList formats = index.data(contentType::data).toMap().keys();
4c75f9e
    if ( emptyIntersection(formats, formatsToSave()) )
4c75f9e
        return nullptr;
4c75f9e
4c75f9e
    const int bytes = preview ? 4096 : m_settings.value("max_bytes", defaultMaxBytes).toInt();
4c75f9e
    return new ItemData(index, bytes, parent);
4c75f9e
}
4c75f9e
4c75f9e
QStringList ItemDataLoader::formatsToSave() const
4c75f9e
{
4c75f9e
    return m_settings.contains("formats")
4c75f9e
            ? m_settings["formats"].toStringList()
4c75f9e
            : QStringList() << mimeUriList << QString("text/xml");
4c75f9e
}
4c75f9e
4c75f9e
QVariantMap ItemDataLoader::applySettings()
4c75f9e
{
4c75f9e
    Q_ASSERT(ui != nullptr);
4c75f9e
    m_settings["formats"] = ui->plainTextEditFormats->toPlainText().split( QRegExp("[;,\\s]+") );
4c75f9e
    m_settings["max_bytes"] = ui->spinBoxMaxChars->value();
4c75f9e
    return  m_settings;
4c75f9e
}
4c75f9e
4c75f9e
QWidget *ItemDataLoader::createSettingsWidget(QWidget *parent)
4c75f9e
{
4c75f9e
    ui.reset(new Ui::ItemDataSettings);
4c75f9e
    QWidget *w = new QWidget(parent);
4c75f9e
    ui->setupUi(w);
4c75f9e
4c75f9e
    const QStringList formats = formatsToSave();
4c75f9e
    ui->plainTextEditFormats->setPlainText( formats.join(QString("\n")) );
4c75f9e
    ui->spinBoxMaxChars->setValue( m_settings.value("max_bytes", defaultMaxBytes).toInt() );
4c75f9e
4c75f9e
    connect( ui->treeWidgetFormats, SIGNAL(itemActivated(QTreeWidgetItem*,int)),
4c75f9e
             SLOT(on_treeWidgetFormats_itemActivated(QTreeWidgetItem*,int)) );
4c75f9e
4c75f9e
    return w;
4c75f9e
}
4c75f9e
4c75f9e
void ItemDataLoader::on_treeWidgetFormats_itemActivated(QTreeWidgetItem *item, int column)
4c75f9e
{
4c75f9e
    const QString mime = item->toolTip(column);
4c75f9e
    if ( !mime.isEmpty() )
4c75f9e
        ui->plainTextEditFormats->appendPlainText(mime);
4c75f9e
}
4c75f9e
4c75f9e
Q_EXPORT_PLUGIN2(itemdata, ItemDataLoader)