1 | /*************************************************************************** |
2 | * Copyright (C) 2005-2014 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 <QPainter> |
22 | #include <QTextDocument> |
23 | #include <QTextLayout> |
24 | |
25 | #include "graphicalui.h" |
26 | #include "styledlabel.h" |
27 | #include "uistyle.h" |
28 | |
29 | StyledLabel::StyledLabel(QWidget *parent) |
30 | : QFrame(parent), |
31 | _wrapMode(QTextOption::NoWrap), |
32 | _alignment(Qt::AlignVCenter|Qt::AlignLeft), |
33 | _toolTipEnabled(true), |
34 | _resizeMode(NoResize) |
35 | { |
36 | setMouseTracking(true); |
37 | |
38 | QTextOption opt = _layout.textOption(); |
39 | opt.setWrapMode(_wrapMode); |
40 | opt.setAlignment(_alignment); |
41 | _layout.setTextOption(opt); |
42 | } |
43 | |
44 | |
45 | void StyledLabel::setCustomFont(const QFont &font) |
46 | { |
47 | setFont(font); |
48 | _layout.setFont(font); |
49 | setText(_layout.text()); |
50 | } |
51 | |
52 | |
53 | void StyledLabel::setWrapMode(QTextOption::WrapMode mode) |
54 | { |
55 | if (_wrapMode == mode) |
56 | return; |
57 | |
58 | _wrapMode = mode; |
59 | QTextOption opt = _layout.textOption(); |
60 | opt.setWrapMode(mode); |
61 | _layout.setTextOption(opt); |
62 | |
63 | layout(); |
64 | } |
65 | |
66 | |
67 | void StyledLabel::setAlignment(Qt::Alignment alignment) |
68 | { |
69 | if (_alignment == alignment) |
70 | return; |
71 | |
72 | _alignment = alignment; |
73 | QTextOption opt = _layout.textOption(); |
74 | opt.setAlignment(alignment); |
75 | _layout.setTextOption(opt); |
76 | |
77 | layout(); |
78 | } |
79 | |
80 | |
81 | void StyledLabel::setResizeMode(ResizeMode mode) |
82 | { |
83 | if (_resizeMode == mode) |
84 | return; |
85 | |
86 | _resizeMode = mode; |
87 | if (mode == DynamicResize) |
88 | setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); |
89 | else |
90 | setWrapMode(QTextOption::NoWrap); |
91 | } |
92 | |
93 | |
94 | void StyledLabel::resizeEvent(QResizeEvent *event) |
95 | { |
96 | QFrame::resizeEvent(event); |
97 | |
98 | layout(); |
99 | } |
100 | |
101 | |
102 | QSize StyledLabel::sizeHint() const |
103 | { |
104 | return _sizeHint; |
105 | } |
106 | |
107 | |
108 | void StyledLabel::updateSizeHint() |
109 | { |
110 | QSize sh; |
111 | int padding = frameWidth() * 2; |
112 | sh = _layout.boundingRect().size().toSize() + QSize(padding, padding); |
113 | |
114 | if (_sizeHint != sh) { |
115 | _sizeHint = sh; |
116 | updateGeometry(); |
117 | } |
118 | } |
119 | |
120 | |
121 | void StyledLabel::setText(const QString &text) |
122 | { |
123 | UiStyle *style = GraphicalUi::uiStyle(); |
124 | |
125 | UiStyle::StyledString sstr = style->styleString(style->mircToInternal(text), UiStyle::PlainMsg); |
126 | QList<QTextLayout::FormatRange> layoutList = style->toTextLayoutList(sstr.formatList, sstr.plainText.length(), 0); |
127 | |
128 | // Use default font rather than the style's |
129 | QTextLayout::FormatRange fmtRange; |
130 | fmtRange.format.setFont(font()); |
131 | fmtRange.start = 0; |
132 | fmtRange.length = sstr.plainText.length(); |
133 | layoutList << fmtRange; |
134 | |
135 | // Mark URLs |
136 | _clickables = ClickableList::fromString(sstr.plainText); |
137 | foreach(Clickable click, _clickables) { |
138 | if (click.type() == Clickable::Url) { |
139 | QTextLayout::FormatRange range; |
140 | range.start = click.start(); |
141 | range.length = click.length(); |
142 | range.format.setForeground(palette().link()); |
143 | layoutList << range; |
144 | } |
145 | } |
146 | |
147 | _layout.setText(sstr.plainText); |
148 | _layout.setAdditionalFormats(layoutList); |
149 | |
150 | layout(); |
151 | |
152 | endHoverMode(); |
153 | } |
154 | |
155 | |
156 | void StyledLabel::updateToolTip() |
157 | { |
158 | if (frameRect().width() - 2*frameWidth() < _layout.minimumWidth()) |
159 | #if QT_VERSION < 0x050000 |
160 | setToolTip(QString("<qt>%1</qt>" ).arg(Qt::escape(_layout.text()))); // only rich text gets wordwrapped! |
161 | #else |
162 | setToolTip(QString("<qt>%1</qt>" ).arg(_layout.text().toHtmlEscaped())); // only rich text gets wordwrapped! |
163 | #endif |
164 | else |
165 | setToolTip(QString()); |
166 | } |
167 | |
168 | |
169 | void StyledLabel::layout() |
170 | { |
171 | qreal h = 0; |
172 | qreal w = contentsRect().width(); |
173 | |
174 | _layout.beginLayout(); |
175 | forever { |
176 | QTextLine line = _layout.createLine(); |
177 | if (!line.isValid()) |
178 | break; |
179 | line.setLineWidth(w); |
180 | line.setPosition(QPointF(0, h)); |
181 | h += line.height(); |
182 | } |
183 | _layout.endLayout(); |
184 | |
185 | updateSizeHint(); |
186 | updateToolTip(); |
187 | update(); |
188 | } |
189 | |
190 | |
191 | void StyledLabel::paintEvent(QPaintEvent *e) |
192 | { |
193 | QFrame::paintEvent(e); |
194 | QPainter painter(this); |
195 | |
196 | qreal y = contentsRect().y() + (contentsRect().height() - _layout.boundingRect().height()) / 2; |
197 | _layout.draw(&painter, QPointF(contentsRect().x(), y), _extraLayoutList); |
198 | } |
199 | |
200 | |
201 | int StyledLabel::posToCursor(const QPointF &pos) |
202 | { |
203 | if (pos.y() < 0 || pos.y() > height()) |
204 | return -1; |
205 | |
206 | for (int l = _layout.lineCount() - 1; l >= 0; l--) { |
207 | QTextLine line = _layout.lineAt(l); |
208 | if (pos.y() >= line.y()) { |
209 | return line.xToCursor(pos.x(), QTextLine::CursorOnCharacter); |
210 | } |
211 | } |
212 | return -1; |
213 | } |
214 | |
215 | |
216 | void StyledLabel::mouseMoveEvent(QMouseEvent *event) |
217 | { |
218 | if (event->buttons() == Qt::NoButton) { |
219 | #if QT_VERSION < 0x050000 |
220 | Clickable click = _clickables.atCursorPos(posToCursor(event->posF())); |
221 | #else |
222 | Clickable click = _clickables.atCursorPos(posToCursor(event->localPos())); |
223 | #endif |
224 | if (click.isValid()) |
225 | setHoverMode(click.start(), click.length()); |
226 | else |
227 | endHoverMode(); |
228 | } |
229 | } |
230 | |
231 | |
232 | void StyledLabel::enterEvent(QEvent *) |
233 | { |
234 | if (resizeMode() == ResizeOnHover) |
235 | setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); |
236 | } |
237 | |
238 | |
239 | void StyledLabel::leaveEvent(QEvent *) |
240 | { |
241 | endHoverMode(); |
242 | if (resizeMode() == ResizeOnHover) |
243 | setWrapMode(QTextOption::NoWrap); |
244 | } |
245 | |
246 | |
247 | void StyledLabel::mousePressEvent(QMouseEvent *event) |
248 | { |
249 | if (event->button() == Qt::LeftButton) { |
250 | #if QT_VERSION < 0x050000 |
251 | Clickable click = _clickables.atCursorPos(posToCursor(event->posF())); |
252 | #else |
253 | Clickable click = _clickables.atCursorPos(posToCursor(event->localPos())); |
254 | #endif |
255 | if (click.isValid()) |
256 | emit clickableActivated(click); |
257 | } |
258 | } |
259 | |
260 | |
261 | void StyledLabel::setHoverMode(int start, int length) |
262 | { |
263 | if (_extraLayoutList.count() >= 1 && _extraLayoutList.first().start == start && _extraLayoutList.first().length == length) |
264 | return; |
265 | |
266 | QTextLayout::FormatRange range; |
267 | range.start = start; |
268 | range.length = length; |
269 | range.format.setFontUnderline(true); |
270 | _extraLayoutList.clear(); |
271 | _extraLayoutList << range; |
272 | |
273 | setCursor(Qt::PointingHandCursor); |
274 | update(); |
275 | } |
276 | |
277 | |
278 | void StyledLabel::endHoverMode() |
279 | { |
280 | _extraLayoutList.clear(); |
281 | setCursor(Qt::ArrowCursor); |
282 | update(); |
283 | } |
284 | |