Make URLs (and channel names!) in the topic widget clickable again
[quassel.git] / src / uisupport / styledlabel.cpp
1 /***************************************************************************
2 *   Copyright (C) 2005-09 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 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, 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 {
35   setMouseTracking(true);
36
37   QTextOption opt = _layout.textOption();
38   opt.setWrapMode(_wrapMode);
39   opt.setAlignment(_alignment);
40   _layout.setTextOption(opt);
41 }
42
43 void StyledLabel::setWrapMode(QTextOption::WrapMode mode) {
44   if(_wrapMode == mode)
45     return;
46
47   _wrapMode = mode;
48   QTextOption opt = _layout.textOption();
49   opt.setWrapMode(mode);
50   _layout.setTextOption(opt);
51
52   layout();
53 }
54
55 void StyledLabel::setAlignment(Qt::Alignment alignment) {
56   if(_alignment == alignment)
57     return;
58
59   _alignment = alignment;
60   QTextOption opt = _layout.textOption();
61   opt.setAlignment(alignment);
62   _layout.setTextOption(opt);
63
64   layout();
65 }
66
67 void StyledLabel::resizeEvent(QResizeEvent *event) {
68   QFrame::resizeEvent(event);
69
70   layout();
71 }
72
73 QSize StyledLabel::sizeHint() const {
74   return _sizeHint;
75 }
76
77 void StyledLabel::updateSizeHint() {
78   QSize sh;
79   int padding = frameWidth() * 2;
80   sh = _layout.boundingRect().size().toSize() + QSize(padding, padding);
81
82   if(_sizeHint != sh) {
83     _sizeHint = sh;
84     updateGeometry();
85   }
86 }
87
88 void StyledLabel::setText(const QString &text) {
89   UiStyle *style = GraphicalUi::uiStyle();
90
91   UiStyle::StyledString sstr = style->styleString(style->mircToInternal(text), UiStyle::PlainMsg);
92   QList<QTextLayout::FormatRange> layoutList = style->toTextLayoutList(sstr.formatList, sstr.plainText.length(), 0);
93
94   // Use default font rather than the style's
95   QTextLayout::FormatRange fmtRange;
96   fmtRange.format.setFont(font());
97   fmtRange.start = 0;
98   fmtRange.length = sstr.plainText.length();
99   layoutList << fmtRange;
100
101   // Mark URLs
102   _clickables = ClickableList::fromString(sstr.plainText);
103   foreach(Clickable click, _clickables) {
104     if(click.type() == Clickable::Url) {
105       QTextLayout::FormatRange range;
106       range.start = click.start();
107       range.length = click.length();
108       range.format.setForeground(palette().link());
109       layoutList << range;
110     }
111   }
112
113   _layout.setText(sstr.plainText);
114   _layout.setAdditionalFormats(layoutList);
115
116   layout();
117
118   endHoverMode();
119 }
120
121 void StyledLabel::updateToolTip() {
122   if(frameRect().width() - 2*frameWidth() < _layout.minimumWidth())
123     setToolTip(QString("<qt>%1</qt>").arg(Qt::escape(_layout.text()))); // only rich text gets wordwrapped!
124   else
125     setToolTip(QString());
126 }
127
128 void StyledLabel::layout() {
129   qreal h = 0;
130   qreal w = frameRect().width() - 2*frameWidth();
131
132   _layout.beginLayout();
133   forever {
134     QTextLine line = _layout.createLine();
135     if(!line.isValid())
136       break;
137     line.setLineWidth(w);
138     line.setPosition(QPointF(0, h));
139     h += line.height();
140   }
141   _layout.endLayout();
142
143   updateSizeHint();
144   updateToolTip();
145   update();
146 }
147
148 void StyledLabel::paintEvent(QPaintEvent *) {
149   QPainter painter(this);
150
151   qreal y = (frameRect().height() - _layout.boundingRect().height()) / 2;
152   _layout.draw(&painter, QPointF(0, y), _extraLayoutList);
153 }
154
155 int StyledLabel::posToCursor(const QPointF &pos) {
156   if(pos.y() < 0 || pos.y() > height())
157     return -1;
158
159   for(int l = _layout.lineCount() - 1; l >= 0; l--) {
160     QTextLine line = _layout.lineAt(l);
161     if(pos.y() >= line.y()) {
162       return line.xToCursor(pos.x(), QTextLine::CursorOnCharacter);
163     }
164   }
165   return -1;
166 }
167
168 void StyledLabel::mouseMoveEvent(QMouseEvent *event) {
169   if(event->buttons() == Qt::NoButton) {
170     Clickable click = _clickables.atCursorPos(posToCursor(event->posF()));
171     if(click.isValid())
172       setHoverMode(click.start(), click.length());
173     else
174       endHoverMode();
175   }
176 }
177
178 void StyledLabel::leaveEvent(QEvent *) {
179   endHoverMode();
180 }
181
182 void StyledLabel::mousePressEvent(QMouseEvent *event) {
183   if(event->button() == Qt::LeftButton) {
184     Clickable click = _clickables.atCursorPos(posToCursor(event->posF()));
185     if(click.isValid())
186       emit clickableActivated(click);
187   }
188 }
189
190 void StyledLabel::setHoverMode(int start, int length) {
191   if(_extraLayoutList.count() >= 1 && _extraLayoutList.first().start == start && _extraLayoutList.first().length == length)
192     return;
193
194   QTextLayout::FormatRange range;
195   range.start = start;
196   range.length = length;
197   range.format.setFontUnderline(true);
198   _extraLayoutList.clear();
199   _extraLayoutList << range;
200
201   setCursor(Qt::PointingHandCursor);
202   update();
203 }
204
205 void StyledLabel::endHoverMode() {
206   _extraLayoutList.clear();
207   setCursor(Qt::ArrowCursor);
208   update();
209 }
210