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#ifndef CHATITEM_H_
22#define CHATITEM_H_
23
24#include <QAction>
25#include <QObject>
26
27#include "chatlinemodel.h"
28#include "chatscene.h"
29#include "clickable.h"
30#include "uistyle.h"
31#include "qtui.h"
32
33#include <QTextLayout>
34
35class ChatLine;
36class ChatView;
37
38/* All external positions are relative to the parent ChatLine */
39/* Yes, that's also true for the boundingRect() and related things */
40
41class ChatItem
42{
43protected:
44 // boundingRect is relative to the parent ChatLine
45 ChatItem(const QRectF &boundingRect, ChatLine *parent);
46 virtual ~ChatItem();
47
48public:
49 const QAbstractItemModel *model() const;
50 ChatLine *chatLine() const;
51 ChatScene *chatScene() const;
52 ChatView *chatView() const;
53 int row() const;
54 virtual ChatLineModel::ColumnType column() const = 0;
55
56 // The boundingRect() is relative to the parent ChatLine
57 inline QRectF boundingRect() const;
58 inline qreal width() const;
59 inline qreal height() const;
60 inline QPointF pos() const;
61 inline qreal x() const;
62 inline qreal y() const;
63
64 QPointF mapToLine(const QPointF &) const;
65 QPointF mapFromLine(const QPointF &) const;
66 QPointF mapToScene(const QPointF &) const;
67 QPointF mapFromScene(const QPointF &) const;
68
69 virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
70 virtual inline int type() const { return ChatScene::ChatItemType; }
71
72 QVariant data(int role) const;
73
74 // selection stuff, to be called by the scene
75 QString selection() const;
76 void clearSelection();
77 void setFullSelection();
78 void continueSelecting(const QPointF &pos);
79 bool hasSelection() const;
80 bool isPosOverSelection(const QPointF &pos) const;
81
82 QList<QRectF> findWords(const QString &searchWord, Qt::CaseSensitivity caseSensitive);
83
84 virtual void addActionsToMenu(QMenu *menu, const QPointF &itemPos);
85 virtual void handleClick(const QPointF &pos, ChatScene::ClickMode);
86
87 void initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft) const;
88
89 //! Remove internally cached data
90 /** This removes e.g. the cached QTextLayout to avoid wasting space for nonvisible ChatLines
91 */
92 virtual void clearCache();
93
94protected:
95 enum SelectionMode {
96 NoSelection,
97 PartialSelection,
98 FullSelection
99 };
100
101 virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
102 virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
103 virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
104 virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *) {}
105 virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *) {}
106 virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *) {}
107
108 QTextLayout *layout() const;
109
110 virtual void initLayout(QTextLayout *layout) const;
111 virtual void doLayout(QTextLayout *) const;
112 virtual UiStyle::FormatList formatList() const;
113
114 void paintBackground(QPainter *);
115 QVector<QTextLayout::FormatRange> selectionFormats() const;
116 virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
117 void overlayFormat(UiStyle::FormatList &fmtList, int start, int end, quint32 overlayFmt) const;
118
119 inline qint16 selectionStart() const { return _selectionStart; }
120 inline void setSelectionStart(qint16 start) { _selectionStart = start; }
121 inline qint16 selectionEnd() const { return _selectionEnd; }
122 inline void setSelectionEnd(qint16 end) { _selectionEnd = end; }
123 inline SelectionMode selectionMode() const { return _selectionMode; }
124 inline void setSelectionMode(SelectionMode mode) { _selectionMode = mode; }
125 void setSelection(SelectionMode mode, qint16 selectionStart, qint16 selectionEnd);
126
127 qint16 posToCursor(const QPointF &pos) const;
128
129 inline void setGeometry(qreal width, qreal height) { clearCache(); _boundingRect.setSize(QSizeF(width, height)); }
130 inline void setHeight(const qreal &height) { clearCache(); _boundingRect.setHeight(height); }
131 inline void setWidth(const qreal &width) { clearCache(); _boundingRect.setWidth(width); }
132 inline void setPos(const QPointF &pos) { _boundingRect.moveTopLeft(pos); }
133
134private:
135 ChatLine *_parent;
136 QRectF _boundingRect;
137
138 SelectionMode _selectionMode;
139 qint16 _selectionStart, _selectionEnd;
140
141 mutable QTextLayout *_cachedLayout;
142
143 // internal selection stuff
144 void setSelection(int start, int length);
145
146 friend class ChatLine;
147};
148
149
150// ************************************************************
151// TimestampChatItem
152// ************************************************************
153
154//! A ChatItem for the timestamp column
155class TimestampChatItem : public ChatItem
156{
157public:
158 TimestampChatItem(const QRectF &boundingRect, ChatLine *parent) : ChatItem(boundingRect, parent) {}
159 virtual inline int type() const { return ChatScene::TimestampChatItemType; }
160 virtual inline ChatLineModel::ColumnType column() const { return ChatLineModel::TimestampColumn; }
161};
162
163
164// ************************************************************
165// SenderChatItem
166// ************************************************************
167//! A ChatItem for the sender column
168class SenderChatItem : public ChatItem
169{
170public:
171 SenderChatItem(const QRectF &boundingRect, ChatLine *parent) : ChatItem(boundingRect, parent) {}
172 virtual inline ChatLineModel::ColumnType column() const { return ChatLineModel::SenderColumn; }
173 virtual void handleClick(const QPointF &pos, ChatScene::ClickMode clickMode);
174
175protected:
176 virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
177 virtual inline int type() const { return ChatScene::SenderChatItemType; }
178 virtual void initLayout(QTextLayout *layout) const;
179};
180
181
182// ************************************************************
183// ContentsChatItem
184// ************************************************************
185struct ContentsChatItemPrivate;
186
187//! A ChatItem for the contents column
188class ContentsChatItem : public ChatItem
189{
190 Q_DECLARE_TR_FUNCTIONS(ContentsChatItem)
191
192public:
193 ContentsChatItem(const QPointF &pos, const qreal &width, ChatLine *parent);
194 ~ContentsChatItem();
195
196 virtual inline int type() const { return ChatScene::ContentsChatItemType; }
197
198 inline ChatLineModel::ColumnType column() const { return ChatLineModel::ContentsColumn; }
199 QFontMetricsF *fontMetrics() const;
200
201 virtual void clearCache();
202
203protected:
204 virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
205 virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
206 virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event);
207 virtual void handleClick(const QPointF &pos, ChatScene::ClickMode clickMode);
208
209 virtual void addActionsToMenu(QMenu *menu, const QPointF &itemPos);
210 virtual void copyLinkToClipboard();
211
212 virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
213
214 virtual void initLayout(QTextLayout *layout) const;
215 virtual void doLayout(QTextLayout *layout) const;
216 virtual UiStyle::FormatList formatList() const;
217
218private:
219 class ActionProxy;
220 class WrapColumnFinder;
221
222 mutable ContentsChatItemPrivate *_data;
223 ContentsChatItemPrivate *privateData() const;
224
225 Clickable clickableAt(const QPointF &pos) const;
226
227 void endHoverMode();
228 void showWebPreview(const Clickable &click);
229 void clearWebPreview();
230
231 qreal setGeometryByWidth(qreal w);
232
233 QFontMetricsF *_fontMetrics;
234
235 // we need a receiver for Action signals
236 static ActionProxy _actionProxy;
237
238 friend class ChatLine;
239 friend struct ContentsChatItemPrivate;
240};
241
242
243struct ContentsChatItemPrivate {
244 ContentsChatItem *contentsItem;
245 ClickableList clickables;
246 Clickable currentClickable;
247 Clickable activeClickable;
248
249 ContentsChatItemPrivate(const ClickableList &c, ContentsChatItem *parent) : contentsItem(parent), clickables(c) {}
250};
251
252class ContentsChatItem::WrapColumnFinder
253{
254public:
255 WrapColumnFinder(const ChatItem *parent);
256 ~WrapColumnFinder();
257
258 qint16 nextWrapColumn(qreal width);
259
260private:
261 const ChatItem *item;
262 QTextLayout layout;
263 QTextLine line;
264 ChatLineModel::WrapList wrapList;
265 qint16 wordidx;
266 qint16 lineCount;
267 qreal choppedTrailing;
268};
269
270
271//! Acts as a proxy for Action signals targetted at a ContentsChatItem
272/** Since a ChatItem is not a QObject, hence cannot receive signals, we use a static ActionProxy
273 * as a receiver instead. This avoids having to handle ChatItem actions (e.g. context menu entries)
274 * outside the ChatItem.
275 */
276class ContentsChatItem::ActionProxy : public QObject
277{
278 Q_OBJECT
279
280public slots:
281 inline void copyLinkToClipboard() { item()->copyLinkToClipboard(); }
282
283private:
284 /// Returns the ContentsChatItem that should receive the action event.
285 /** For efficiency reasons, values are not checked for validity. You gotta make sure that you set the data() member
286 * in the Action correctly.
287 * @return The ChatItem from which the sending Action originated
288 */
289 inline ContentsChatItem *item() const
290 {
291 return static_cast<ContentsChatItem *>(qobject_cast<QAction *>(sender())->data().value<void *>());
292 }
293};
294
295
296/*************************************************************************************************/
297
298// Inlines
299
300QRectF ChatItem::boundingRect() const { return _boundingRect; }
301qreal ChatItem::width() const { return _boundingRect.width(); }
302qreal ChatItem::height() const { return _boundingRect.height(); }
303QPointF ChatItem::pos() const { return _boundingRect.topLeft(); }
304qreal ChatItem::x() const { return pos().x(); }
305qreal ChatItem::y() const { return pos().y(); }
306
307#endif
308