1 /***************************************************************************
2 * Copyright (C) 2005-2022 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
23 #include <QAbstractItemModel>
25 #include <QGraphicsSceneMouseEvent>
26 #include <QGraphicsSceneHoverEvent>
29 #include <QStyleOptionGraphicsItem>
30 #include <QTextCharFormat>
32 class QAbstractItemModel;
33 class QGraphicsSceneMouseEvent;
34 class QGraphicsSceneHoverEvent;
36 class QStyleOptionGraphicsItem;
38 #include "bufferinfo.h"
39 #include "buffersyncer.h"
43 #include "columnhandleitem.h"
44 #include "messagemodel.h"
45 #include "networkmodel.h"
47 #include "qtuisettings.h"
48 #include "qtuistyle.h"
50 ChatLine::ChatLine(int row,
51 QAbstractItemModel* model,
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
62 , _contentsItem(contentsPos, contentsWidth, this)
63 , _senderItem(QRectF(senderPos, QSizeF(senderWidth, _contentsItem.height())), this)
64 , _timestampItem(QRectF(0, 0, timestampWidth, _contentsItem.height()), this)
66 , _height(_contentsItem.height())
68 , _mouseGrabberItem(nullptr)
72 QModelIndex index = model->index(row, ChatLineModel::ContentsColumn);
74 setAcceptHoverEvents(true);
75 setHighlighted(index.data(MessageModel::FlagsRole).toInt() & Message::Highlight);
81 chatView()->setHasCache(this, false);
84 ChatItem* ChatLine::item(ChatLineModel::ColumnType column)
87 case ChatLineModel::TimestampColumn:
88 return &_timestampItem;
89 case ChatLineModel::SenderColumn:
91 case ChatLineModel::ContentsColumn:
92 return &_contentsItem;
98 ChatItem* ChatLine::itemAt(const QPointF& pos)
100 if (_contentsItem.boundingRect().contains(pos))
101 return &_contentsItem;
102 if (_senderItem.boundingRect().contains(pos))
104 if (_timestampItem.boundingRect().contains(pos))
105 return &_timestampItem;
109 void ChatLine::clearCache()
111 _timestampItem.clearCache();
112 _senderItem.clearCache();
113 _contentsItem.clearCache();
116 void ChatLine::setMouseGrabberItem(ChatItem* item)
118 _mouseGrabberItem = item;
121 bool ChatLine::sceneEvent(QEvent* event)
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));
129 else if (event->type() == QEvent::UngrabMouse) {
130 setMouseGrabberItem(nullptr);
132 return QGraphicsItem::sceneEvent(event);
135 void ChatLine::setFirstColumn(const qreal& timestampWidth, const qreal& senderWidth, const QPointF& senderPos)
137 _timestampItem.setGeometry(timestampWidth, _height);
138 _senderItem.setGeometry(senderWidth, _height);
139 _senderItem.setPos(senderPos);
142 void ChatLine::setSecondColumn(const qreal& senderWidth, const qreal& contentsWidth, const QPointF& contentsPos, qreal& linePos)
144 // linepos is the *bottom* position for the line
145 qreal height = _contentsItem.setGeometryByWidth(contentsWidth);
147 bool needGeometryChange = (height != _height);
149 _timestampItem.setHeight(height);
150 _senderItem.setGeometry(senderWidth, height);
151 _contentsItem.setPos(contentsPos);
153 if (needGeometryChange)
154 prepareGeometryChange();
161 void ChatLine::setGeometryByWidth(const qreal& width, const qreal& contentsWidth, qreal& linePos)
163 // linepos is the *bottom* position for the line
164 qreal height = _contentsItem.setGeometryByWidth(contentsWidth);
166 bool needGeometryChange = (height != _height || width != _width);
168 if (height != _height) {
169 _timestampItem.setHeight(height);
170 _senderItem.setHeight(height);
173 if (needGeometryChange) {
174 prepareGeometryChange();
179 setPos(0, linePos); // set pos is _very_ cheap if nothing changes.
182 void ChatLine::setSelected(bool selected, ChatLineModel::ColumnType minColumn)
185 quint8 sel = (_selection & Highlighted) | Selected | minColumn;
186 if (sel != _selection) {
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();
196 quint8 sel = _selection & Highlighted;
197 if (sel != _selection) {
199 for (int i = 0; i <= ChatLineModel::ContentsColumn; i++)
200 item((ChatLineModel::ColumnType)i)->clearSelection();
206 void ChatLine::setHighlighted(bool highlighted)
209 _selection |= Highlighted;
211 _selection &= ~Highlighted;
215 void ChatLine::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
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>();
225 QTextCharFormat msgFmt = QtUi::style()->format({UiStyle::formatType(type), {}, {}}, label);
226 if (msgFmt.hasProperty(QTextFormat::BackgroundBrush)) {
227 painter->fillRect(boundingRect(), msgFmt.background());
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());
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);
246 // We need to dispatch all mouse-related events to the appropriate (mouse grabbing) ChatItem
248 ChatItem* ChatLine::mouseEventTargetItem(const QPointF& pos)
250 if (mouseGrabberItem())
251 return mouseGrabberItem();
255 void ChatLine::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
257 ChatItem* item = mouseEventTargetItem(event->pos());
259 item->mouseMoveEvent(event);
262 void ChatLine::mousePressEvent(QGraphicsSceneMouseEvent* event)
264 ChatItem* item = mouseEventTargetItem(event->pos());
266 item->mousePressEvent(event);
269 void ChatLine::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
271 ChatItem* item = mouseEventTargetItem(event->pos());
273 item->mouseReleaseEvent(event);
276 void ChatLine::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
278 ChatItem* item = mouseEventTargetItem(event->pos());
279 if (item && !_hoverItem) {
281 item->hoverEnterEvent(event);
285 void ChatLine::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
288 _hoverItem->hoverLeaveEvent(event);
289 _hoverItem = nullptr;
293 void ChatLine::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
295 ChatItem* item = mouseEventTargetItem(event->pos());
297 item->hoverMoveEvent(event);