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