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