File indexing completed on 2024-04-28 03:59:14

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000 Ronny Standtke <Ronny.Standtke@gmx.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "ksqueezedtextlabel.h"
0009 #include <QAction>
0010 #include <QApplication>
0011 #include <QClipboard>
0012 #include <QContextMenuEvent>
0013 #include <QMenu>
0014 #include <QRegularExpression>
0015 #include <QScreen>
0016 #include <QTextDocument>
0017 
0018 class KSqueezedTextLabelPrivate
0019 {
0020 public:
0021     void copyFullText()
0022     {
0023         QApplication::clipboard()->setText(fullText);
0024     }
0025 
0026     QString fullText;
0027     Qt::TextElideMode elideMode;
0028 };
0029 
0030 KSqueezedTextLabel::KSqueezedTextLabel(const QString &text, QWidget *parent)
0031     : QLabel(parent)
0032     , d(new KSqueezedTextLabelPrivate)
0033 {
0034     setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
0035     d->fullText = text;
0036     d->elideMode = Qt::ElideMiddle;
0037     squeezeTextToLabel();
0038 }
0039 
0040 KSqueezedTextLabel::KSqueezedTextLabel(QWidget *parent)
0041     : QLabel(parent)
0042     , d(new KSqueezedTextLabelPrivate)
0043 {
0044     setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
0045     d->elideMode = Qt::ElideMiddle;
0046 }
0047 
0048 KSqueezedTextLabel::~KSqueezedTextLabel() = default;
0049 
0050 void KSqueezedTextLabel::resizeEvent(QResizeEvent *)
0051 {
0052     squeezeTextToLabel();
0053 }
0054 
0055 QSize KSqueezedTextLabel::minimumSizeHint() const
0056 {
0057     QSize sh = QLabel::minimumSizeHint();
0058     sh.setWidth(-1);
0059     return sh;
0060 }
0061 
0062 QSize KSqueezedTextLabel::sizeHint() const
0063 {
0064     if (!isSqueezed()) {
0065         return QLabel::sizeHint();
0066     }
0067     int maxWidth = screen()->geometry().width() * 3 / 4;
0068     QFontMetrics fm(fontMetrics());
0069     // Do exactly like qlabel.cpp to avoid slight differences in results
0070     // (see https://invent.kde.org/frameworks/kwidgetsaddons/-/merge_requests/100)
0071     int textWidth = fm.boundingRect(0, 0, 2000, 2000, Qt::AlignAbsolute | Qt::TextExpandTabs | Qt::AlignLeft, d->fullText).width();
0072     if (textWidth > maxWidth) {
0073         textWidth = maxWidth;
0074     }
0075     const int chromeWidth = width() - contentsRect().width();
0076     return QSize(textWidth + chromeWidth, QLabel::sizeHint().height());
0077 }
0078 
0079 void KSqueezedTextLabel::setIndent(int indent)
0080 {
0081     QLabel::setIndent(indent);
0082     squeezeTextToLabel();
0083 }
0084 
0085 void KSqueezedTextLabel::setMargin(int margin)
0086 {
0087     QLabel::setMargin(margin);
0088     squeezeTextToLabel();
0089 }
0090 
0091 void KSqueezedTextLabel::setText(const QString &text)
0092 {
0093     d->fullText = text;
0094     squeezeTextToLabel();
0095 }
0096 
0097 void KSqueezedTextLabel::clear()
0098 {
0099     d->fullText.clear();
0100     QLabel::clear();
0101 }
0102 
0103 void KSqueezedTextLabel::squeezeTextToLabel()
0104 {
0105     QFontMetrics fm(fontMetrics());
0106     const int labelWidth = contentsRect().width();
0107     QStringList squeezedLines;
0108     bool squeezed = false;
0109     const auto textLines = d->fullText.split(QLatin1Char('\n'));
0110     squeezedLines.reserve(textLines.size());
0111     for (const QString &line : textLines) {
0112         int lineWidth = fm.boundingRect(line).width();
0113         if (lineWidth > labelWidth) {
0114             squeezed = true;
0115             squeezedLines << fm.elidedText(line, d->elideMode, labelWidth);
0116         } else {
0117             squeezedLines << line;
0118         }
0119     }
0120 
0121     if (squeezed) {
0122         QLabel::setText(squeezedLines.join(QLatin1Char('\n')));
0123         setToolTip(d->fullText);
0124     } else {
0125         QLabel::setText(d->fullText);
0126         setToolTip(QString());
0127     }
0128 }
0129 
0130 QRect KSqueezedTextLabel::contentsRect() const
0131 {
0132     // calculation according to API docs for QLabel::indent
0133     const int margin = this->margin();
0134     int indent = this->indent();
0135     if (indent < 0) {
0136         if (frameWidth() == 0) {
0137             indent = 0;
0138         } else {
0139             indent = fontMetrics().horizontalAdvance(QLatin1Char('x')) / 2 - margin;
0140         }
0141     }
0142 
0143     QRect result = QLabel::contentsRect();
0144     if (indent > 0) {
0145         const int alignment = this->alignment();
0146         if (alignment & Qt::AlignLeft) {
0147             result.setLeft(result.left() + indent);
0148         }
0149         if (alignment & Qt::AlignTop) {
0150             result.setTop(result.top() + indent);
0151         }
0152         if (alignment & Qt::AlignRight) {
0153             result.setRight(result.right() - indent);
0154         }
0155         if (alignment & Qt::AlignBottom) {
0156             result.setBottom(result.bottom() - indent);
0157         }
0158     }
0159 
0160     result.adjust(margin, margin, -margin, -margin);
0161     return result;
0162 }
0163 
0164 void KSqueezedTextLabel::setAlignment(Qt::Alignment alignment)
0165 {
0166     // save fullText and restore it
0167     QString tmpFull(d->fullText);
0168     QLabel::setAlignment(alignment);
0169     d->fullText = tmpFull;
0170 }
0171 
0172 Qt::TextElideMode KSqueezedTextLabel::textElideMode() const
0173 {
0174     return d->elideMode;
0175 }
0176 
0177 void KSqueezedTextLabel::setTextElideMode(Qt::TextElideMode mode)
0178 {
0179     d->elideMode = mode;
0180     squeezeTextToLabel();
0181 }
0182 
0183 QString KSqueezedTextLabel::fullText() const
0184 {
0185     return d->fullText;
0186 }
0187 
0188 bool KSqueezedTextLabel::isSqueezed() const
0189 {
0190     return d->fullText != text();
0191 }
0192 
0193 void KSqueezedTextLabel::contextMenuEvent(QContextMenuEvent *ev)
0194 {
0195     // We want to reimplement "Copy" to include the elided text.
0196     // But this means reimplementing the full popup menu, so no more
0197     // copy-link-address or copy-selection support anymore, since we
0198     // have no access to the QTextDocument.
0199     // Maybe we should have a boolean flag in KSqueezedTextLabel itself for
0200     // whether to show the "Copy Full Text" custom popup?
0201     // For now I chose to show it when the text is squeezed; when it's not, the
0202     // standard popup menu can do the job (select all, copy).
0203 
0204     if (isSqueezed()) {
0205         QMenu menu(this);
0206 
0207         QAction *act = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("&Copy Full Text", "@action:inmenu"), &menu);
0208         connect(act, &QAction::triggered, this, [this]() {
0209             d->copyFullText();
0210         });
0211         menu.addAction(act);
0212 
0213         ev->accept();
0214         menu.exec(ev->globalPos());
0215     } else {
0216         QLabel::contextMenuEvent(ev);
0217     }
0218 }
0219 
0220 void KSqueezedTextLabel::mouseReleaseEvent(QMouseEvent *ev)
0221 {
0222     if (QApplication::clipboard()->supportsSelection() //
0223         && textInteractionFlags() != Qt::NoTextInteraction //
0224         && ev->button() == Qt::LeftButton //
0225         && !d->fullText.isEmpty() //
0226         && hasSelectedText()) {
0227         // Expand "..." when selecting with the mouse
0228         QString txt = selectedText();
0229         const QChar ellipsisChar(0x2026); // from qtextengine.cpp
0230         const int dotsPos = txt.indexOf(ellipsisChar);
0231         if (dotsPos > -1) {
0232             // Ex: abcde...yz, selecting de...y  (selectionStart=3)
0233             // charsBeforeSelection = selectionStart = 2 (ab)
0234             // charsAfterSelection = 1 (z)
0235             // final selection length= 26 - 2 - 1 = 23
0236             const int start = selectionStart();
0237             int charsAfterSelection = text().length() - start - selectedText().length();
0238             txt = d->fullText;
0239             // Strip markup tags
0240             if (textFormat() == Qt::RichText //
0241                 || (textFormat() == Qt::AutoText && Qt::mightBeRichText(txt))) {
0242                 txt.remove(QRegularExpression(QStringLiteral("<[^>]*>")));
0243                 // account for stripped characters
0244                 charsAfterSelection -= d->fullText.length() - txt.length();
0245             }
0246             txt = txt.mid(selectionStart(), txt.length() - start - charsAfterSelection);
0247         }
0248         QApplication::clipboard()->setText(txt, QClipboard::Selection);
0249     } else {
0250         QLabel::mouseReleaseEvent(ev);
0251     }
0252 }
0253 
0254 #include "moc_ksqueezedtextlabel.cpp"