cf12defd28
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
255 lines
7.3 KiB
C++
255 lines
7.3 KiB
C++
/*
|
|
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"
|