File indexing completed on 2025-02-16 13:11:51
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"