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