Remove the word boundary cache
[quassel.git] / src / qtui / chatitem.h
1 /***************************************************************************
2  *   Copyright (C) 2005-09 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  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #ifndef CHATITEM_H_
22 #define CHATITEM_H_
23
24 #include <QAction>
25 #include <QGraphicsItem>
26 #include <QObject>
27
28 #include "chatlinemodel.h"
29 #include "chatscene.h"
30 #include "uistyle.h"
31 #include "qtui.h"
32
33 #include <QTextLayout>
34
35 class ChatItem : public QGraphicsItem {
36 protected:
37   ChatItem(const qreal &width, const qreal &height, const QPointF &pos, QGraphicsItem *parent);
38
39 public:
40   inline const QAbstractItemModel *model() const;
41   inline int row() const;
42   virtual ChatLineModel::ColumnType column() const = 0;
43   inline ChatScene *chatScene() const { return qobject_cast<ChatScene *>(scene()); }
44
45   inline QRectF boundingRect() const { return _boundingRect; }
46   inline qreal width() const { return _boundingRect.width(); }
47   inline qreal height() const { return _boundingRect.height(); }
48
49   void initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft) const;
50   virtual inline void initLayout(QTextLayout *layout) const {
51     initLayoutHelper(layout, QTextOption::WrapAnywhere);
52     doLayout(layout);
53   }
54   virtual void doLayout(QTextLayout *) const;
55
56   virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
57   enum { Type = ChatScene::ChatItemType };
58   virtual inline int type() const { return Type; }
59
60   QVariant data(int role) const;
61
62   // selection stuff, to be called by the scene
63   QString selection() const;
64   void clearSelection();
65   void setFullSelection();
66   void continueSelecting(const QPointF &pos);
67   bool hasSelection() const;
68   bool isPosOverSelection(const QPointF &pos) const;
69
70   QList<QRectF> findWords(const QString &searchWord, Qt::CaseSensitivity caseSensitive);
71
72   virtual void addActionsToMenu(QMenu *menu, const QPointF &itemPos);
73   virtual void handleClick(const QPointF &pos, ChatScene::ClickMode);
74
75 protected:
76   enum SelectionMode {
77     NoSelection,
78     PartialSelection,
79     FullSelection
80   };
81
82   virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
83   virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
84   virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
85
86   void paintBackground(QPainter *);
87   QVector<QTextLayout::FormatRange> selectionFormats() const;
88   virtual inline QVector<QTextLayout::FormatRange> additionalFormats() const { return QVector<QTextLayout::FormatRange>(); }
89
90   inline qint16 selectionStart() const { return _selectionStart; }
91   inline void setSelectionStart(qint16 start) { _selectionStart = start; }
92   inline qint16 selectionEnd() const { return _selectionEnd; }
93   inline void setSelectionEnd(qint16 end) { _selectionEnd = end; }
94   inline SelectionMode selectionMode() const { return _selectionMode; }
95   inline void setSelectionMode(SelectionMode mode) { _selectionMode = mode; }
96   void setSelection(SelectionMode mode, qint16 selectionStart, qint16 selectionEnd);
97
98   qint16 posToCursor(const QPointF &pos) const;
99
100   // WARNING: setGeometry and setHeight should not be used without either:
101   //  a) calling prepareGeometryChange() immediately before setColumns()
102   //  b) calling Chatline::setPos() immediately afterwards
103   inline void setGeometry(qreal width, qreal height) {
104     _boundingRect.setWidth(width);
105     _boundingRect.setHeight(height);
106   }
107   inline void setHeight(const qreal &height) {
108     _boundingRect.setHeight(height);
109   }
110   inline void setWidth(const qreal &width) {
111     _boundingRect.setWidth(width);
112   }
113
114 private:
115   // internal selection stuff
116   void setSelection(int start, int length);
117
118   QRectF _boundingRect;
119
120   SelectionMode _selectionMode;
121   qint16 _selectionStart, _selectionEnd;
122
123   friend class ChatLine;
124 };
125
126 // ************************************************************
127 // TimestampChatItem
128 // ************************************************************
129
130 //! A ChatItem for the timestamp column
131 class TimestampChatItem : public ChatItem {
132 public:
133   TimestampChatItem(const qreal &width, const qreal &height, QGraphicsItem *parent) : ChatItem(width, height, QPointF(0, 0), parent) {}
134   enum { Type = ChatScene::TimestampChatItemType };
135   virtual inline int type() const { return Type; }
136   virtual inline ChatLineModel::ColumnType column() const { return ChatLineModel::TimestampColumn; }
137 };
138
139 // ************************************************************
140 // SenderChatItem
141 // ************************************************************
142 //! A ChatItem for the sender column
143 class SenderChatItem : public ChatItem {
144 public:
145   SenderChatItem(const qreal &width, const qreal &height, const QPointF &pos, QGraphicsItem *parent) : ChatItem(width, height, pos, parent) {}
146   virtual inline ChatLineModel::ColumnType column() const { return ChatLineModel::SenderColumn; }
147
148 protected:
149   virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
150   enum { Type = ChatScene::SenderChatItemType };
151   virtual inline int type() const { return Type; }
152   virtual inline void initLayout(QTextLayout *layout) const {
153     initLayoutHelper(layout, QTextOption::ManualWrap, Qt::AlignRight);
154     doLayout(layout);
155   }
156 };
157
158 // ************************************************************
159 // ContentsChatItem
160 // ************************************************************
161 struct ContentsChatItemPrivate;
162
163 //! A ChatItem for the contents column
164 class ContentsChatItem : public ChatItem {
165   Q_DECLARE_TR_FUNCTIONS(ContentsChatItem);
166
167 public:
168   ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent);
169   ~ContentsChatItem();
170
171   enum { Type = ChatScene::ContentsChatItemType };
172   virtual inline int type() const { return Type; }
173
174   inline ChatLineModel::ColumnType column() const { return ChatLineModel::ContentsColumn; }
175   QFontMetricsF *fontMetrics() const;
176
177 protected:
178   virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
179   virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
180   virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event);
181   virtual void handleClick(const QPointF &pos, ChatScene::ClickMode clickMode);
182
183   virtual void addActionsToMenu(QMenu *menu, const QPointF &itemPos);
184   virtual void copyLinkToClipboard();
185
186   virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
187
188   virtual inline void initLayout(QTextLayout *layout) const {
189     initLayoutHelper(layout, QTextOption::WrapAtWordBoundaryOrAnywhere);
190     doLayout(layout);
191   }
192
193 private:
194   struct Clickable;
195   class ActionProxy;
196
197   ContentsChatItemPrivate *_data;
198   ContentsChatItemPrivate *privateData() const;
199
200   QList<Clickable> findClickables() const;
201   Clickable clickableAt(const QPointF &pos) const;
202
203   void endHoverMode();
204   void showWebPreview(const Clickable &click);
205   void clearWebPreview();
206
207   qreal setGeometryByWidth(qreal w);
208   friend class ChatLine;
209   friend struct ContentsChatItemPrivate;
210
211   QFontMetricsF *_fontMetrics;
212
213   // we need a receiver for Action signals
214   static ActionProxy _actionProxy;
215 };
216
217 struct ContentsChatItem::Clickable {
218   // Don't change these enums without also changing the regexps in analyze()!
219   enum Type {
220     Invalid = -1,
221     Url = 0,
222     Channel = 1,
223     Nick = 2
224   };
225
226   Type type;
227   quint16 start;
228   quint16 length;
229
230   inline Clickable() : type(Invalid) {};
231   inline Clickable(Type type_, quint16 start_, quint16 length_) : type(type_), start(start_), length(length_) {};
232   inline bool isValid() const { return type != Invalid; }
233 };
234
235 struct ContentsChatItemPrivate {
236   ContentsChatItem *contentsItem;
237   QList<ContentsChatItem::Clickable> clickables;
238   ContentsChatItem::Clickable currentClickable;
239   ContentsChatItem::Clickable activeClickable;
240
241   ContentsChatItemPrivate(const QList<ContentsChatItem::Clickable> &c, ContentsChatItem *parent) : contentsItem(parent), clickables(c) {}
242 };
243
244 //! Acts as a proxy for Action signals targetted at a ContentsChatItem
245 /** Since a ChatItem is not a QObject, hence cannot receive signals, we use a static ActionProxy
246  *  as a receiver instead. This avoids having to handle ChatItem actions (e.g. context menu entries)
247  *  outside the ChatItem.
248  */
249 class ContentsChatItem::ActionProxy : public QObject {
250   Q_OBJECT
251
252 public slots:
253   inline void copyLinkToClipboard() { item()->copyLinkToClipboard(); }
254
255 private:
256   /// Returns the ContentsChatItem that should receive the action event.
257   /** For efficiency reasons, values are not checked for validity. You gotta make sure that you set the data() member
258    *  in the Action correctly.
259    *  @return The ChatItem from which the sending Action originated
260    */
261   inline ContentsChatItem *item() const {
262     return static_cast<ContentsChatItem *>(qobject_cast<QAction *>(sender())->data().value<void *>());
263   }
264 };
265
266 /*************************************************************************************************/
267
268 // Avoid circular include deps
269 #include "chatline.h"
270 const QAbstractItemModel *ChatItem::model() const { return static_cast<ChatLine *>(parentItem())->model(); }
271 int ChatItem::row() const { return static_cast<ChatLine *>(parentItem())->row(); }
272
273 #endif