Implement proper layouting for QmlChatLine
[quassel.git] / src / qmlui / qmlchatline.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2011 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 #include <QApplication>
21 #include <QPainter>
22
23 #include "graphicalui.h"
24 #include "qmlchatline.h"
25
26 void QmlChatLine::registerTypes() {
27   qRegisterMetaType<RenderData>("QmlChatLine::RenderData");
28   qRegisterMetaTypeStreamOperators<RenderData>("QmlChatLine::RenderData");
29   qmlRegisterType<QmlChatLine>("eu.quassel.qml", 1, 0, "ChatLine");
30 }
31
32 QDataStream &operator<<(QDataStream &out, const QmlChatLine::RenderData &data) {
33   out << data.isValid << data.messageLabel;
34   for(int i = 0; i < (int)QmlChatLine::NumColumns; ++i) {
35     const QmlChatLine::RenderData::Column &col = data[static_cast<QmlChatLine::ColumnType>(i)];
36     out << col.text << col.formats << col.background << col.selectedBackground;
37   }
38   return out;
39 }
40
41 QDataStream &operator>>(QDataStream &in, QmlChatLine::RenderData &data) {
42   in >> data.isValid >> data.messageLabel;
43   for(int i = 0; i < (int)QmlChatLine::NumColumns; ++i) {
44     QmlChatLine::RenderData::Column &col = data[static_cast<QmlChatLine::ColumnType>(i)];
45     in >> col.text >> col.formats >> col.background >> col.selectedBackground;
46   }
47   return in;
48 }
49
50 QmlChatLine::QmlChatLine(QDeclarativeItem *parent)
51   : QDeclarativeItem(parent),
52     _timestampWidth(0),
53     _senderWidth(0),
54     _contentsWidth(0),
55     _layout(0)
56 {
57   setFlag(ItemHasNoContents, false);
58   setImplicitHeight(QApplication::fontMetrics().height());
59   setImplicitWidth(1000);
60   connect(this, SIGNAL(columnWidthChanged(ColumnType)), SLOT(onColumnWidthChanged(ColumnType)));
61 }
62
63 QmlChatLine::~QmlChatLine() {
64
65 }
66
67 void QmlChatLine::setTimestampWidth(qreal w) {
68   if(w != _timestampWidth) {
69     _timestampWidth = w;
70     emit timestampWidthChanged(w);
71     emit columnWidthChanged(TimestampColumn);
72   }
73 }
74
75 void QmlChatLine::setSenderWidth(qreal w) {
76   if(w != _senderWidth) {
77     _senderWidth = w;
78     emit senderWidthChanged(w);
79     emit columnWidthChanged(SenderColumn);
80   }
81 }
82
83 void QmlChatLine::setContentsWidth(qreal w) {
84   if(w != _contentsWidth) {
85     _contentsWidth = w;
86
87     if(renderData().isValid)
88       layout()->compute();
89
90     emit contentsWidthChanged(w);
91     emit columnWidthChanged(ContentsColumn);
92   }
93 }
94
95 void QmlChatLine::setColumnSpacing(qreal s) {
96   if(s != _columnSpacing) {
97     _columnSpacing = s;
98     emit columnSpacingChanged(s);
99   }
100 }
101
102 QPointF QmlChatLine::columnPos(ColumnType colType) const {
103   switch(colType) {
104   case TimestampColumn:
105     return QPointF(0, 0);
106   case SenderColumn:
107     return QPointF(timestampWidth(), 0);
108   case ContentsColumn:
109     return QPointF(timestampWidth() + senderWidth(), 0);
110   default:
111     return QPointF();
112   }
113 }
114
115 qreal QmlChatLine::columnWidth(ColumnType colType) const {
116   switch(colType) {
117   case TimestampColumn:
118     return timestampWidth();
119   case SenderColumn:
120     return senderWidth();
121   case ContentsColumn:
122     return contentsWidth();
123   default:
124     return 0;
125   }
126 }
127
128 QRectF QmlChatLine::columnBoundingRect(ColumnType colType) const {
129   QRectF rect;
130   switch(colType) {
131   case TimestampColumn:
132     return QRectF(columnPos(TimestampColumn), QSizeF(timestampWidth() - columnSpacing(), implicitHeight()));
133   case SenderColumn:
134     return QRectF(columnPos(SenderColumn), QSizeF(senderWidth() - columnSpacing(), implicitHeight()));
135   case ContentsColumn:
136     return QRectF(columnPos(ContentsColumn), QSizeF(contentsWidth(), implicitHeight()));
137   default:
138     return QRectF();
139   }
140 }
141
142 void QmlChatLine::setRenderData(const RenderData &data) {
143   _data = data;
144
145   if(_layout) {
146     delete _layout;
147     _layout = 0;
148   }
149
150   //update();
151 }
152
153 QmlChatLine::Layout *QmlChatLine::layout() {
154   if(!_layout) {
155     _layout = new Layout(this);
156   }
157   return _layout;
158 }
159
160 void QmlChatLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
161   Q_UNUSED(option)
162   Q_UNUSED(widget)
163   if(renderData().isValid)
164     layout()->draw(painter);
165 }
166
167 void QmlChatLine::onColumnWidthChanged(ColumnType colType) {
168   Q_UNUSED(colType)
169   setImplicitWidth(_timestampWidth + _senderWidth + _contentsWidth);
170   update();
171 }
172
173 /**************************************************************************************/
174
175 QmlChatLine::Layout::Layout(QmlChatLine *parent)
176   : _parent(parent)
177 {
178   _timestampLayout = new TimestampLayout(parent);
179   _senderLayout = new SenderLayout(parent);
180   _contentsLayout = new ContentsLayout(parent);
181 }
182
183 QmlChatLine::Layout::~Layout() {
184   delete _timestampLayout;
185   delete _senderLayout;
186   delete _contentsLayout;
187 }
188
189 qreal QmlChatLine::Layout::height() const {
190   return _contentsLayout->height();
191 }
192
193 void QmlChatLine::Layout::compute() {
194   _timestampLayout->compute();
195   _senderLayout->compute();
196   _contentsLayout->compute();
197 }
198
199 void QmlChatLine::Layout::draw(QPainter *p) {
200   _timestampLayout->draw(p);
201   _senderLayout->draw(p);
202   _contentsLayout->draw(p);
203 }
204
205 /*************/
206
207 QmlChatLine::ColumnLayout::ColumnLayout(QmlChatLine::ColumnType type, QmlChatLine *parent)
208   : _parent(parent),
209     _type(type),
210     _layout(0)
211 {
212
213 }
214
215 QmlChatLine::ColumnLayout::~ColumnLayout() {
216   delete _layout;
217 }
218
219 void QmlChatLine::ColumnLayout::initLayout(QTextOption::WrapMode wrapMode, Qt::Alignment alignment) {
220   if(_layout)
221     delete _layout;
222
223   const RenderData::Column &data = chatLine()->renderData()[columnType()];
224   _layout = new QTextLayout(data.text);
225
226   QTextOption option;
227   option.setWrapMode(wrapMode);
228   option.setAlignment(alignment);
229   layout()->setTextOption(option);
230
231   QList<QTextLayout::FormatRange> formats = GraphicalUi::uiStyle()->toTextLayoutList(data.formats, layout()->text().length(), chatLine()->renderData().messageLabel);
232   layout()->setAdditionalFormats(formats);
233
234   compute();
235 }
236
237 qreal QmlChatLine::ColumnLayout::height() const {
238   return layout()->boundingRect().height();
239 }
240
241 void QmlChatLine::ColumnLayout::compute() {
242   qreal width = chatLine()->columnBoundingRect(columnType()).width();
243   qreal h = 0;
244   layout()->beginLayout();
245   forever {
246     QTextLine line = layout()->createLine();
247     if(!line.isValid())
248       break;
249
250     line.setLineWidth(width);
251     line.setPosition(QPointF(0, h));
252     h += line.height();
253   }
254   layout()->endLayout();
255 }
256
257 void QmlChatLine::ColumnLayout::draw(QPainter *p) {
258   p->save();
259
260   QRectF rect = chatLine()->columnBoundingRect(columnType());
261
262   qreal layoutWidth = layout()->minimumWidth();
263   qreal offset = 0;
264
265   if(layout()->textOption().alignment() == Qt::AlignRight) {
266     /*
267       if(chatScene()->senderCutoffMode() == ChatScene::CutoffLeft)
268         offset = qMin(width() - layoutWidth, (qreal)0);
269       else
270         offset = qMax(layoutWidth - width(), (qreal)0);
271     */
272       offset = qMax(layoutWidth - rect.width(), (qreal)0);
273   }
274
275   if(layoutWidth > rect.width()) {
276     // Draw a nice gradient for longer items
277
278     QLinearGradient gradient;
279     if(offset < 0) {
280       gradient.setStart(0, 0);
281       gradient.setFinalStop(12, 0);
282       gradient.setColorAt(0, Qt::transparent);
283       gradient.setColorAt(1, Qt::white);
284     } else {
285       gradient.setStart(rect.width()-12, 0);
286       gradient.setFinalStop(rect.width(), 0);
287       gradient.setColorAt(0, Qt::white);
288       gradient.setColorAt(1, Qt::transparent);
289     }
290
291     QImage img(layout()->boundingRect().toRect().size(), QImage::Format_ARGB32_Premultiplied);
292     //img.fill(Qt::transparent);
293     QPainter imgPainter(&img);
294     imgPainter.fillRect(img.rect(), gradient);
295     imgPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
296     layout()->draw(&imgPainter, QPointF(qMax(offset, (qreal)0), 0), selectionFormats());
297     imgPainter.end();
298     p->drawImage(rect.topLeft(), img);
299   } else {
300     layout()->draw(p, rect.topLeft(), selectionFormats(), rect);
301   }
302
303   p->restore();
304 }
305
306 QVector<QTextLayout::FormatRange> QmlChatLine::ColumnLayout::selectionFormats() const {
307   return QVector<QTextLayout::FormatRange>();
308 }
309
310 /**************/
311
312 QmlChatLine::TimestampLayout::TimestampLayout(QmlChatLine *chatLine)
313   : ColumnLayout(TimestampColumn, chatLine)
314 {
315   initLayout(QTextOption::NoWrap, Qt::AlignLeft);
316 }
317
318
319 /**************/
320
321 QmlChatLine::SenderLayout::SenderLayout(QmlChatLine *chatLine)
322   : ColumnLayout(SenderColumn, chatLine)
323 {
324   initLayout(QTextOption::NoWrap, Qt::AlignRight);
325 }
326
327
328 /**************/
329
330 QmlChatLine::ContentsLayout::ContentsLayout(QmlChatLine *chatLine)
331   : ColumnLayout(ContentsColumn, chatLine)
332 {
333   initLayout(QTextOption::WrapAtWordBoundaryOrAnywhere, Qt::AlignLeft);
334 }
335
336 void QmlChatLine::ContentsLayout::compute() {
337   ColumnLayout::compute();
338   chatLine()->setImplicitHeight(layout()->boundingRect().height());
339 }