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