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