1 /***************************************************************************
2 * Copyright (C) 2005-2020 by the Quassel Project *
3 * devel@quassel-irc.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21 #include "styledlabel.h"
22
23 #include <QPainter>
24 #include <QTextDocument>
25 #include <QTextLayout>
26
27 #include "graphicalui.h"
28 #include "uistyle.h"
29
StyledLabel(QWidget * parent)30 StyledLabel::StyledLabel(QWidget* parent)
31 : QFrame(parent)
32 , _alignment(Qt::AlignVCenter | Qt::AlignLeft)
33 {
34 setMouseTracking(true);
35
36 QTextOption opt = _layout.textOption();
37 opt.setWrapMode(_wrapMode);
38 opt.setAlignment(_alignment);
39 _layout.setTextOption(opt);
40 }
41
setCustomFont(const QFont & font)42 void StyledLabel::setCustomFont(const QFont& font)
43 {
44 setFont(font);
45 _layout.setFont(font);
46 setText(_layout.text());
47 }
48
setWrapMode(QTextOption::WrapMode mode)49 void StyledLabel::setWrapMode(QTextOption::WrapMode mode)
50 {
51 if (_wrapMode == mode)
52 return;
53
54 _wrapMode = mode;
55 QTextOption opt = _layout.textOption();
56 opt.setWrapMode(mode);
57 _layout.setTextOption(opt);
58
59 layout();
60 }
61
setAlignment(Qt::Alignment alignment)62 void StyledLabel::setAlignment(Qt::Alignment alignment)
63 {
64 if (_alignment == alignment)
65 return;
66
67 _alignment = alignment;
68 QTextOption opt = _layout.textOption();
69 opt.setAlignment(alignment);
70 _layout.setTextOption(opt);
71
72 layout();
73 }
74
setResizeMode(ResizeMode mode)75 void StyledLabel::setResizeMode(ResizeMode mode)
76 {
77 if (_resizeMode == mode)
78 return;
79
80 _resizeMode = mode;
81 if (mode == DynamicResize)
82 setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
83 else
84 setWrapMode(QTextOption::NoWrap);
85 }
86
resizeEvent(QResizeEvent * event)87 void StyledLabel::resizeEvent(QResizeEvent* event)
88 {
89 QFrame::resizeEvent(event);
90
91 layout();
92 }
93
sizeHint() const94 QSize StyledLabel::sizeHint() const
95 {
96 return _sizeHint;
97 }
98
updateSizeHint()99 void StyledLabel::updateSizeHint()
100 {
101 QSize sh;
102 int padding = frameWidth() * 2;
103 sh = _layout.boundingRect().size().toSize() + QSize(padding, padding);
104
105 if (_sizeHint != sh) {
106 _sizeHint = sh;
107 updateGeometry();
108 }
109 }
110
setText(const QString & text)111 void StyledLabel::setText(const QString& text)
112 {
113 UiStyle::StyledString sstr = UiStyle::styleString(UiStyle::mircToInternal(text), UiStyle::FormatType::PlainMsg);
114 UiStyle::FormatContainer layoutList = GraphicalUi::uiStyle()->toTextLayoutList(sstr.formatList, sstr.plainText.length(), UiStyle::MessageLabel::None);
115
116 // Use default font rather than the style's
117 QTextLayout::FormatRange fmtRange;
118 fmtRange.format.setFont(font());
119 fmtRange.start = 0;
120 fmtRange.length = sstr.plainText.length();
121 layoutList << fmtRange;
122
123 // Mark URLs
124 _clickables = ClickableList::fromString(sstr.plainText);
125 foreach (Clickable click, _clickables) {
126 if (click.type() == Clickable::Url) {
127 QTextLayout::FormatRange range;
128 range.start = click.start();
129 range.length = click.length();
130 range.format.setForeground(palette().link());
131 layoutList << range;
132 }
133 }
134
135 _layout.setText(sstr.plainText);
136 UiStyle::setTextLayoutFormats(_layout, layoutList);
137
138 layout();
139
140 endHoverMode();
141 }
142
updateToolTip()143 void StyledLabel::updateToolTip()
144 {
145 if (frameRect().width() - 2 * frameWidth() < _layout.minimumWidth())
146 setToolTip(QString("<qt>%1</qt>").arg(_layout.text().toHtmlEscaped())); // only rich text gets wordwrapped!
147 else
148 setToolTip(QString());
149 }
150
layout()151 void StyledLabel::layout()
152 {
153 qreal h = 0;
154 qreal w = contentsRect().width();
155
156 _layout.beginLayout();
157 forever
158 {
159 QTextLine line = _layout.createLine();
160 if (!line.isValid())
161 break;
162 line.setLineWidth(w);
163 line.setPosition(QPointF(0, h));
164 h += line.height();
165 }
166 _layout.endLayout();
167
168 updateSizeHint();
169 updateToolTip();
170 update();
171 }
172
paintEvent(QPaintEvent * e)173 void StyledLabel::paintEvent(QPaintEvent* e)
174 {
175 QFrame::paintEvent(e);
176 QPainter painter(this);
177
178 qreal y = contentsRect().y() + (contentsRect().height() - _layout.boundingRect().height()) / 2;
179 _layout.draw(&painter, QPointF(contentsRect().x(), y), _extraLayoutList);
180 }
181
posToCursor(const QPointF & pos)182 int StyledLabel::posToCursor(const QPointF& pos)
183 {
184 if (pos.y() < 0 || pos.y() > height())
185 return -1;
186
187 for (int l = _layout.lineCount() - 1; l >= 0; l--) {
188 QTextLine line = _layout.lineAt(l);
189 if (pos.y() >= line.y()) {
190 return line.xToCursor(pos.x(), QTextLine::CursorOnCharacter);
191 }
192 }
193 return -1;
194 }
195
mouseMoveEvent(QMouseEvent * event)196 void StyledLabel::mouseMoveEvent(QMouseEvent* event)
197 {
198 if (event->buttons() == Qt::NoButton) {
199 Clickable click = _clickables.atCursorPos(posToCursor(event->localPos()));
200 if (click.isValid())
201 setHoverMode(click.start(), click.length());
202 else
203 endHoverMode();
204 }
205 }
206
enterEvent(QEvent *)207 void StyledLabel::enterEvent(QEvent*)
208 {
209 if (resizeMode() == ResizeOnHover)
210 setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
211 }
212
leaveEvent(QEvent *)213 void StyledLabel::leaveEvent(QEvent*)
214 {
215 endHoverMode();
216 if (resizeMode() == ResizeOnHover)
217 setWrapMode(QTextOption::NoWrap);
218 }
219
mousePressEvent(QMouseEvent * event)220 void StyledLabel::mousePressEvent(QMouseEvent* event)
221 {
222 if (event->button() == Qt::LeftButton) {
223 Clickable click = _clickables.atCursorPos(posToCursor(event->localPos()));
224 if (click.isValid())
225 emit clickableActivated(click);
226 }
227 }
228
setHoverMode(int start,int length)229 void StyledLabel::setHoverMode(int start, int length)
230 {
231 if (_extraLayoutList.count() >= 1 && _extraLayoutList.first().start == start && _extraLayoutList.first().length == length)
232 return;
233
234 QTextLayout::FormatRange range;
235 range.start = start;
236 range.length = length;
237 range.format.setFontUnderline(true);
238 _extraLayoutList.clear();
239 _extraLayoutList << range;
240
241 setCursor(Qt::PointingHandCursor);
242 update();
243 }
244
endHoverMode()245 void StyledLabel::endHoverMode()
246 {
247 _extraLayoutList.clear();
248 setCursor(Qt::ArrowCursor);
249 update();
250 }
251