22dd9a702cc59a7a513a0a5da3511f17cc8be685
[quassel.git] / src / qtui / chatline.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2019 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 "chatline.h"
22
23 #include <QDateTime>
24 #include <QGraphicsSceneMouseEvent>
25 #include <QString>
26 #include <QtGui>
27
28 #include "bufferinfo.h"
29 #include "buffersyncer.h"
30 #include "chatitem.h"
31 #include "chatview.h"
32 #include "client.h"
33 #include "columnhandleitem.h"
34 #include "messagemodel.h"
35 #include "networkmodel.h"
36 #include "qtui.h"
37 #include "qtuisettings.h"
38 #include "qtuistyle.h"
39
40 ChatLine::ChatLine(int row,
41                    QAbstractItemModel* model,
42                    const qreal& width,
43                    const qreal& timestampWidth,
44                    const qreal& senderWidth,
45                    const qreal& contentsWidth,
46                    const QPointF& senderPos,
47                    const QPointF& contentsPos,
48                    QGraphicsItem* parent)
49     : QGraphicsItem(parent)
50     , _row(row)
51     ,  // needs to be set before the items
52     _model(model)
53     , _contentsItem(contentsPos, contentsWidth, this)
54     , _senderItem(QRectF(senderPos, QSizeF(senderWidth, _contentsItem.height())), this)
55     , _timestampItem(QRectF(0, 0, timestampWidth, _contentsItem.height()), this)
56     , _width(width)
57     , _height(_contentsItem.height())
58     , _selection(0)
59     , _mouseGrabberItem(nullptr)
60     , _hoverItem(nullptr)
61 {
62     Q_ASSERT(model);
63     QModelIndex index = model->index(row, ChatLineModel::ContentsColumn);
64     setZValue(0);
65     setAcceptHoverEvents(true);
66     setHighlighted(index.data(MessageModel::FlagsRole).toInt() & Message::Highlight);
67 }
68
69 ChatLine::~ChatLine()
70 {
71     if (chatView())
72         chatView()->setHasCache(this, false);
73 }
74
75 ChatItem* ChatLine::item(ChatLineModel::ColumnType column)
76 {
77     switch (column) {
78     case ChatLineModel::TimestampColumn:
79         return &_timestampItem;
80     case ChatLineModel::SenderColumn:
81         return &_senderItem;
82     case ChatLineModel::ContentsColumn:
83         return &_contentsItem;
84     default:
85         return nullptr;
86     }
87 }
88
89 ChatItem* ChatLine::itemAt(const QPointF& pos)
90 {
91     if (_contentsItem.boundingRect().contains(pos))
92         return &_contentsItem;
93     if (_senderItem.boundingRect().contains(pos))
94         return &_senderItem;
95     if (_timestampItem.boundingRect().contains(pos))
96         return &_timestampItem;
97     return nullptr;
98 }
99
100 void ChatLine::clearCache()
101 {
102     _timestampItem.clearCache();
103     _senderItem.clearCache();
104     _contentsItem.clearCache();
105 }
106
107 void ChatLine::setMouseGrabberItem(ChatItem* item)
108 {
109     _mouseGrabberItem = item;
110 }
111
112 bool ChatLine::sceneEvent(QEvent* event)
113 {
114     if (event->type() == QEvent::GrabMouse) {
115         // get mouse cursor pos relative to us
116         ChatView* view = chatScene()->chatView();
117         QPointF linePos = mapFromScene(view->mapToScene(view->mapFromGlobal(QCursor::pos())));
118         setMouseGrabberItem(itemAt(linePos));
119     }
120     else if (event->type() == QEvent::UngrabMouse) {
121         setMouseGrabberItem(nullptr);
122     }
123     return QGraphicsItem::sceneEvent(event);
124 }
125
126 void ChatLine::setFirstColumn(const qreal& timestampWidth, const qreal& senderWidth, const QPointF& senderPos)
127 {
128     _timestampItem.setGeometry(timestampWidth, _height);
129     _senderItem.setGeometry(senderWidth, _height);
130     _senderItem.setPos(senderPos);
131 }
132
133 void ChatLine::setSecondColumn(const qreal& senderWidth, const qreal& contentsWidth, const QPointF& contentsPos, qreal& linePos)
134 {
135     // linepos is the *bottom* position for the line
136     qreal height = _contentsItem.setGeometryByWidth(contentsWidth);
137     linePos -= height;
138     bool needGeometryChange = (height != _height);
139
140     _timestampItem.setHeight(height);
141     _senderItem.setGeometry(senderWidth, height);
142     _contentsItem.setPos(contentsPos);
143
144     if (needGeometryChange)
145         prepareGeometryChange();
146
147     _height = height;
148
149     setPos(0, linePos);
150 }
151
152 void ChatLine::setGeometryByWidth(const qreal& width, const qreal& contentsWidth, qreal& linePos)
153 {
154     // linepos is the *bottom* position for the line
155     qreal height = _contentsItem.setGeometryByWidth(contentsWidth);
156     linePos -= height;
157     bool needGeometryChange = (height != _height || width != _width);
158
159     if (height != _height) {
160         _timestampItem.setHeight(height);
161         _senderItem.setHeight(height);
162     }
163
164     if (needGeometryChange) {
165         prepareGeometryChange();
166         _height = height;
167         _width = width;
168     }
169
170     setPos(0, linePos);  // set pos is _very_ cheap if nothing changes.
171 }
172
173 void ChatLine::setSelected(bool selected, ChatLineModel::ColumnType minColumn)
174 {
175     if (selected) {
176         quint8 sel = (_selection & Highlighted) | Selected | minColumn;
177         if (sel != _selection) {
178             _selection = sel;
179             for (int i = 0; i < minColumn; i++)
180                 item((ChatLineModel::ColumnType)i)->clearSelection();
181             for (int i = minColumn; i <= ChatLineModel::ContentsColumn; i++)
182                 item((ChatLineModel::ColumnType)i)->setFullSelection();
183             update();
184         }
185     }
186     else {
187         quint8 sel = _selection & Highlighted;
188         if (sel != _selection) {
189             _selection = sel;
190             for (int i = 0; i <= ChatLineModel::ContentsColumn; i++)
191                 item((ChatLineModel::ColumnType)i)->clearSelection();
192             update();
193         }
194     }
195 }
196
197 void ChatLine::setHighlighted(bool highlighted)
198 {
199     if (highlighted)
200         _selection |= Highlighted;
201     else
202         _selection &= ~Highlighted;
203     update();
204 }
205
206 void ChatLine::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
207 {
208     Q_UNUSED(option);
209     Q_UNUSED(widget);
210
211     const QAbstractItemModel* model_ = model();
212     QModelIndex myIdx = model_->index(row(), 0);
213     Message::Type type = (Message::Type)myIdx.data(MessageModel::TypeRole).toInt();
214     auto label = myIdx.data(ChatLineModel::MsgLabelRole).value<UiStyle::MessageLabel>();
215
216     QTextCharFormat msgFmt = QtUi::style()->format({UiStyle::formatType(type), {}, {}}, label);
217     if (msgFmt.hasProperty(QTextFormat::BackgroundBrush)) {
218         painter->fillRect(boundingRect(), msgFmt.background());
219     }
220
221     if (_selection & Selected) {
222         QTextCharFormat selFmt = QtUi::style()->format({UiStyle::formatType(type), {}, {}}, label | UiStyle::MessageLabel::Selected);
223         if (selFmt.hasProperty(QTextFormat::BackgroundBrush)) {
224             qreal left = item((ChatLineModel::ColumnType)(_selection & ItemMask))->pos().x();
225             QRectF selectRect(left, 0, width() - left, height());
226             painter->fillRect(selectRect, selFmt.background());
227         }
228     }
229
230     // draw chatitems
231     // the items draw themselves at the correct position
232     timestampItem()->paint(painter, option, widget);
233     senderItem()->paint(painter, option, widget);
234     contentsItem()->paint(painter, option, widget);
235 }
236
237 // We need to dispatch all mouse-related events to the appropriate (mouse grabbing) ChatItem
238
239 ChatItem* ChatLine::mouseEventTargetItem(const QPointF& pos)
240 {
241     if (mouseGrabberItem())
242         return mouseGrabberItem();
243     return itemAt(pos);
244 }
245
246 void ChatLine::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
247 {
248     ChatItem* item = mouseEventTargetItem(event->pos());
249     if (item)
250         item->mouseMoveEvent(event);
251 }
252
253 void ChatLine::mousePressEvent(QGraphicsSceneMouseEvent* event)
254 {
255     ChatItem* item = mouseEventTargetItem(event->pos());
256     if (item)
257         item->mousePressEvent(event);
258 }
259
260 void ChatLine::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
261 {
262     ChatItem* item = mouseEventTargetItem(event->pos());
263     if (item)
264         item->mouseReleaseEvent(event);
265 }
266
267 void ChatLine::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
268 {
269     ChatItem* item = mouseEventTargetItem(event->pos());
270     if (item && !_hoverItem) {
271         _hoverItem = item;
272         item->hoverEnterEvent(event);
273     }
274 }
275
276 void ChatLine::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
277 {
278     if (_hoverItem) {
279         _hoverItem->hoverLeaveEvent(event);
280         _hoverItem = nullptr;
281     }
282 }
283
284 void ChatLine::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
285 {
286     ChatItem* item = mouseEventTargetItem(event->pos());
287     if (item)
288         item->hoverMoveEvent(event);
289 }