/*
    This file is part of the KDE libraries
    SPDX-FileCopyrightText: 2000 Ronny Standtke <Ronny.Standtke@gmx.de>

    SPDX-License-Identifier: LGPL-2.0-only
*/

#include "ksqueezedtextlabel.h"
#include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QContextMenuEvent>
#include <QMenu>
#include <QRegularExpression>
#include <QScreen>
#include <QTextDocument>

class KSqueezedTextLabelPrivate
{
public:
    void copyFullText()
    {
        QApplication::clipboard()->setText(fullText);
    }

    QString fullText;
    Qt::TextElideMode elideMode;
};

KSqueezedTextLabel::KSqueezedTextLabel(const QString &text, QWidget *parent)
    : QLabel(parent)
    , d(new KSqueezedTextLabelPrivate)
{
    setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
    d->fullText = text;
    d->elideMode = Qt::ElideMiddle;
    squeezeTextToLabel();
}

KSqueezedTextLabel::KSqueezedTextLabel(QWidget *parent)
    : QLabel(parent)
    , d(new KSqueezedTextLabelPrivate)
{
    setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
    d->elideMode = Qt::ElideMiddle;
}

KSqueezedTextLabel::~KSqueezedTextLabel() = default;

void KSqueezedTextLabel::resizeEvent(QResizeEvent *)
{
    squeezeTextToLabel();
}

QSize KSqueezedTextLabel::minimumSizeHint() const
{
    QSize sh = QLabel::minimumSizeHint();
    sh.setWidth(-1);
    return sh;
}

QSize KSqueezedTextLabel::sizeHint() const
{
    if (!isSqueezed()) {
        return QLabel::sizeHint();
    }
    int maxWidth = screen()->geometry().width() * 3 / 4;
    QFontMetrics fm(fontMetrics());
    // Do exactly like qlabel.cpp to avoid slight differences in results
    // (see https://invent.kde.org/frameworks/kwidgetsaddons/-/merge_requests/100)
    int textWidth = fm.boundingRect(0, 0, 2000, 2000, Qt::AlignAbsolute | Qt::TextExpandTabs | Qt::AlignLeft, d->fullText).width();
    if (textWidth > maxWidth) {
        textWidth = maxWidth;
    }
    const int chromeWidth = width() - contentsRect().width();
    return QSize(textWidth + chromeWidth, QLabel::sizeHint().height());
}

void KSqueezedTextLabel::setIndent(int indent)
{
    QLabel::setIndent(indent);
    squeezeTextToLabel();
}

void KSqueezedTextLabel::setMargin(int margin)
{
    QLabel::setMargin(margin);
    squeezeTextToLabel();
}

void KSqueezedTextLabel::setText(const QString &text)
{
    d->fullText = text;
    squeezeTextToLabel();
}

void KSqueezedTextLabel::clear()
{
    d->fullText.clear();
    QLabel::clear();
}

void KSqueezedTextLabel::squeezeTextToLabel()
{
    QFontMetrics fm(fontMetrics());
    const int labelWidth = contentsRect().width();
    QStringList squeezedLines;
    bool squeezed = false;
    const auto textLines = d->fullText.split(QLatin1Char('\n'));
    squeezedLines.reserve(textLines.size());
    for (const QString &line : textLines) {
        int lineWidth = fm.boundingRect(line).width();
        if (lineWidth > labelWidth) {
            squeezed = true;
            squeezedLines << fm.elidedText(line, d->elideMode, labelWidth);
        } else {
            squeezedLines << line;
        }
    }

    if (squeezed) {
        QLabel::setText(squeezedLines.join(QLatin1Char('\n')));
        setToolTip(d->fullText);
    } else {
        QLabel::setText(d->fullText);
        setToolTip(QString());
    }
}

QRect KSqueezedTextLabel::contentsRect() const
{
    // calculation according to API docs for QLabel::indent
    const int margin = this->margin();
    int indent = this->indent();
    if (indent < 0) {
        if (frameWidth() == 0) {
            indent = 0;
        } else {
            indent = fontMetrics().horizontalAdvance(QLatin1Char('x')) / 2 - margin;
        }
    }

    QRect result = QLabel::contentsRect();
    if (indent > 0) {
        const int alignment = this->alignment();
        if (alignment & Qt::AlignLeft) {
            result.setLeft(result.left() + indent);
        }
        if (alignment & Qt::AlignTop) {
            result.setTop(result.top() + indent);
        }
        if (alignment & Qt::AlignRight) {
            result.setRight(result.right() - indent);
        }
        if (alignment & Qt::AlignBottom) {
            result.setBottom(result.bottom() - indent);
        }
    }

    result.adjust(margin, margin, -margin, -margin);
    return result;
}

void KSqueezedTextLabel::setAlignment(Qt::Alignment alignment)
{
    // save fullText and restore it
    QString tmpFull(d->fullText);
    QLabel::setAlignment(alignment);
    d->fullText = tmpFull;
}

Qt::TextElideMode KSqueezedTextLabel::textElideMode() const
{
    return d->elideMode;
}

void KSqueezedTextLabel::setTextElideMode(Qt::TextElideMode mode)
{
    d->elideMode = mode;
    squeezeTextToLabel();
}

QString KSqueezedTextLabel::fullText() const
{
    return d->fullText;
}

bool KSqueezedTextLabel::isSqueezed() const
{
    return d->fullText != text();
}

void KSqueezedTextLabel::contextMenuEvent(QContextMenuEvent *ev)
{
    // We want to reimplement "Copy" to include the elided text.
    // But this means reimplementing the full popup menu, so no more
    // copy-link-address or copy-selection support anymore, since we
    // have no access to the QTextDocument.
    // Maybe we should have a boolean flag in KSqueezedTextLabel itself for
    // whether to show the "Copy Full Text" custom popup?
    // For now I chose to show it when the text is squeezed; when it's not, the
    // standard popup menu can do the job (select all, copy).

    if (isSqueezed()) {
        QMenu menu(this);

        QAction *act = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("&Copy Full Text", "@action:inmenu"), &menu);
        connect(act, &QAction::triggered, this, [this]() {
            d->copyFullText();
        });
        menu.addAction(act);

        ev->accept();
        menu.exec(ev->globalPos());
    } else {
        QLabel::contextMenuEvent(ev);
    }
}

void KSqueezedTextLabel::mouseReleaseEvent(QMouseEvent *ev)
{
    if (QApplication::clipboard()->supportsSelection() //
        && textInteractionFlags() != Qt::NoTextInteraction //
        && ev->button() == Qt::LeftButton //
        && !d->fullText.isEmpty() //
        && hasSelectedText()) {
        // Expand "..." when selecting with the mouse
        QString txt = selectedText();
        const QChar ellipsisChar(0x2026); // from qtextengine.cpp
        const int dotsPos = txt.indexOf(ellipsisChar);
        if (dotsPos > -1) {
            // Ex: abcde...yz, selecting de...y  (selectionStart=3)
            // charsBeforeSelection = selectionStart = 2 (ab)
            // charsAfterSelection = 1 (z)
            // final selection length= 26 - 2 - 1 = 23
            const int start = selectionStart();
            int charsAfterSelection = text().length() - start - selectedText().length();
            txt = d->fullText;
            // Strip markup tags
            if (textFormat() == Qt::RichText //
                || (textFormat() == Qt::AutoText && Qt::mightBeRichText(txt))) {
                txt.remove(QRegularExpression(QStringLiteral("<[^>]*>")));
                // account for stripped characters
                charsAfterSelection -= d->fullText.length() - txt.length();
            }
            txt = txt.mid(selectionStart(), txt.length() - start - charsAfterSelection);
        }
        QApplication::clipboard()->setText(txt, QClipboard::Selection);
    } else {
        QLabel::mouseReleaseEvent(ev);
    }
}

#include "moc_ksqueezedtextlabel.cpp"
