Add /raw as an alias for /quote
[quassel.git] / src / qtui / chatitem.h
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 #ifndef CHATITEM_H_
22 #define CHATITEM_H_
23
24 #include <utility>
25
26 #include <QAction>
27 #include <QObject>
28 #include <QTextLayout>
29
30 #include "chatlinemodel.h"
31 #include "chatscene.h"
32 #include "clickable.h"
33 #include "qtui.h"
34 #include "uistyle.h"
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     {
98         NoSelection,
99         PartialSelection,
100         FullSelection
101     };
102
103     virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event);
104     virtual void mousePressEvent(QGraphicsSceneMouseEvent* event);
105     virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event);
106     virtual void hoverEnterEvent(QGraphicsSceneHoverEvent*) {}
107     virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent*) {}
108     virtual void hoverMoveEvent(QGraphicsSceneHoverEvent*) {}
109
110     QTextLayout* layout() const;
111
112     virtual void initLayout(QTextLayout* layout) const;
113     virtual void doLayout(QTextLayout*) const;
114     virtual UiStyle::FormatList formatList() const;
115
116     void paintBackground(QPainter*);
117     virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
118     void overlayFormat(UiStyle::FormatList& fmtList, quint16 start, quint16 end, UiStyle::FormatType overlayFmt) const;
119
120     inline qint16 selectionStart() const { return _selectionStart; }
121     inline void setSelectionStart(qint16 start) { _selectionStart = start; }
122     inline qint16 selectionEnd() const { return _selectionEnd; }
123     inline void setSelectionEnd(qint16 end) { _selectionEnd = end; }
124     inline SelectionMode selectionMode() const { return _selectionMode; }
125     inline void setSelectionMode(SelectionMode mode) { _selectionMode = mode; }
126     void setSelection(SelectionMode mode, qint16 selectionStart, qint16 selectionEnd);
127
128     virtual bool hasActiveClickable() const;
129     virtual std::pair<quint16, quint16> activeClickableRange() const;
130
131     qint16 posToCursor(const QPointF& pos) const;
132
133     inline void setGeometry(qreal width, qreal height)
134     {
135         clearCache();
136         _boundingRect.setSize(QSizeF(width, height));
137     }
138     inline void setHeight(const qreal& height)
139     {
140         clearCache();
141         _boundingRect.setHeight(height);
142     }
143     inline void setWidth(const qreal& width)
144     {
145         clearCache();
146         _boundingRect.setWidth(width);
147     }
148     inline void setPos(const QPointF& pos) { _boundingRect.moveTopLeft(pos); }
149
150 private:
151     ChatLine* _parent;
152     QRectF _boundingRect;
153
154     SelectionMode _selectionMode;
155     qint16 _selectionStart, _selectionEnd;
156
157     mutable QTextLayout* _cachedLayout;
158
159     // internal selection stuff
160     void setSelection(int start, int length);
161
162     friend class ChatLine;
163 };
164
165 // ************************************************************
166 // TimestampChatItem
167 // ************************************************************
168
169 //! A ChatItem for the timestamp column
170 class TimestampChatItem : public ChatItem
171 {
172 public:
173     TimestampChatItem(const QRectF& boundingRect, ChatLine* parent)
174         : ChatItem(boundingRect, parent)
175     {}
176     inline int type() const override { return ChatScene::TimestampChatItemType; }
177     inline ChatLineModel::ColumnType column() const override { return ChatLineModel::TimestampColumn; }
178 };
179
180 // ************************************************************
181 // SenderChatItem
182 // ************************************************************
183 //! A ChatItem for the sender column
184 class SenderChatItem : public ChatItem
185 {
186 public:
187     SenderChatItem(const QRectF& boundingRect, ChatLine* parent)
188         : ChatItem(boundingRect, parent)
189     {}
190     inline ChatLineModel::ColumnType column() const override { return ChatLineModel::SenderColumn; }
191     void handleClick(const QPointF& pos, ChatScene::ClickMode clickMode) override;
192
193 protected:
194     void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;
195     inline int type() const override { return ChatScene::SenderChatItemType; }
196     void initLayout(QTextLayout* layout) const override;
197 };
198
199 // ************************************************************
200 // ContentsChatItem
201 // ************************************************************
202 struct ContentsChatItemPrivate;
203
204 //! A ChatItem for the contents column
205 class ContentsChatItem : public ChatItem
206 {
207     Q_DECLARE_TR_FUNCTIONS(ContentsChatItem)
208
209 public:
210     ContentsChatItem(const QPointF& pos, const qreal& width, ChatLine* parent);
211     ~ContentsChatItem() override;
212
213     inline int type() const override { return ChatScene::ContentsChatItemType; }
214
215     inline ChatLineModel::ColumnType column() const override { return ChatLineModel::ContentsColumn; }
216     QFontMetricsF* fontMetrics() const;
217
218     void clearCache() override;
219
220 protected:
221     void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
222     void hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override;
223     void hoverMoveEvent(QGraphicsSceneHoverEvent* event) override;
224     void handleClick(const QPointF& pos, ChatScene::ClickMode clickMode) override;
225
226     bool hasActiveClickable() const override;
227     std::pair<quint16, quint16> activeClickableRange() const override;
228
229     void addActionsToMenu(QMenu* menu, const QPointF& itemPos) override;
230     virtual void copyLinkToClipboard();
231
232     void initLayout(QTextLayout* layout) const override;
233     void doLayout(QTextLayout* layout) const override;
234     UiStyle::FormatList formatList() const override;
235
236 private:
237     class ActionProxy;
238     class WrapColumnFinder;
239
240     mutable ContentsChatItemPrivate* _data;
241     ContentsChatItemPrivate* privateData() const;
242
243     Clickable clickableAt(const QPointF& pos) const;
244
245     void endHoverMode();
246     void showWebPreview(const Clickable& click);
247     void clearWebPreview();
248
249     qreal setGeometryByWidth(qreal w);
250
251     QFontMetricsF* _fontMetrics;
252
253     // we need a receiver for Action signals
254     static ActionProxy _actionProxy;
255
256     friend class ChatLine;
257     friend struct ContentsChatItemPrivate;
258 };
259
260 struct ContentsChatItemPrivate
261 {
262     ContentsChatItem* contentsItem;
263     ClickableList clickables;
264     Clickable currentClickable;
265     Clickable activeClickable;
266
267     ContentsChatItemPrivate(ClickableList c, ContentsChatItem* parent)
268         : contentsItem(parent)
269         , clickables(std::move(c))
270     {}
271 };
272
273 class ContentsChatItem::WrapColumnFinder
274 {
275 public:
276     WrapColumnFinder(const ChatItem* parent);
277
278     qint16 nextWrapColumn(qreal width);
279
280 private:
281     const ChatItem* item;
282     QTextLayout layout;
283     QTextLine line;
284     ChatLineModel::WrapList wrapList;
285     qint16 wordidx;
286     qint16 lineCount;
287     qreal choppedTrailing;
288 };
289
290 //! Acts as a proxy for Action signals targetted at a ContentsChatItem
291 /** Since a ChatItem is not a QObject, hence cannot receive signals, we use a static ActionProxy
292  *  as a receiver instead. This avoids having to handle ChatItem actions (e.g. context menu entries)
293  *  outside the ChatItem.
294  */
295 class ContentsChatItem::ActionProxy : public QObject
296 {
297     Q_OBJECT
298
299 public slots:
300     inline void copyLinkToClipboard() { item()->copyLinkToClipboard(); }
301
302 private:
303     /// Returns the ContentsChatItem that should receive the action event.
304     /** For efficiency reasons, values are not checked for validity. You gotta make sure that you set the data() member
305      *  in the Action correctly.
306      *  @return The ChatItem from which the sending Action originated
307      */
308     inline ContentsChatItem* item() const
309     {
310         return static_cast<ContentsChatItem*>(qobject_cast<QAction*>(sender())->data().value<void*>());
311     }
312 };
313
314 /*************************************************************************************************/
315
316 // Inlines
317
318 QRectF ChatItem::boundingRect() const
319 {
320     return _boundingRect;
321 }
322 qreal ChatItem::width() const
323 {
324     return _boundingRect.width();
325 }
326 qreal ChatItem::height() const
327 {
328     return _boundingRect.height();
329 }
330 QPointF ChatItem::pos() const
331 {
332     return _boundingRect.topLeft();
333 }
334 qreal ChatItem::x() const
335 {
336     return pos().x();
337 }
338 qreal ChatItem::y() const
339 {
340     return pos().y();
341 }
342
343 #endif