Fix chatline caching on resize
[quassel.git] / src / qtui / chatlinemodelitem.cpp
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 #include <QFontMetrics>
22 #include <QTextBoundaryFinder>
23
24 #include "chatlinemodelitem.h"
25 #include "chatlinemodel.h"
26 #include "qtui.h"
27 #include "qtuistyle.h"
28
29 // This Struct is taken from Harfbuzz. We use it only to calc it's size.
30 // we use a shared memory region so we do not have to malloc a buffer area for every line
31 typedef struct {
32     /*HB_LineBreakType*/ unsigned lineBreakType  :2;
33     /*HB_Bool*/ unsigned whiteSpace              :1;     /* A unicode whitespace character, except NBSP, ZWNBSP */
34     /*HB_Bool*/ unsigned charStop                :1;     /* Valid cursor position (for left/right arrow) */
35     /*HB_Bool*/ unsigned wordBoundary            :1;
36     /*HB_Bool*/ unsigned sentenceBoundary        :1;
37     unsigned unused                  :2;
38 } HB_CharAttributes_Dummy;
39
40
41 unsigned char *ChatLineModelItem::TextBoundaryFinderBuffer = (unsigned char *)malloc(512 * sizeof(HB_CharAttributes_Dummy));
42 int ChatLineModelItem::TextBoundaryFinderBufferSize = 512 * (sizeof(HB_CharAttributes_Dummy) / sizeof(unsigned char));
43
44 // ****************************************
45 // the actual ChatLineModelItem
46 // ****************************************
47 ChatLineModelItem::ChatLineModelItem(const Message &msg)
48   : MessageModelItem(),
49     _styledMsg(msg)
50 {
51   if(!msg.sender().contains('!'))
52     _styledMsg.setFlags(msg.flags() |= Message::ServerMsg);
53 }
54
55 bool ChatLineModelItem::setData(int column, const QVariant &value, int role) {
56   switch(role) {
57     case MessageModel::FlagsRole:
58       _styledMsg.setFlags((Message::Flags)value.toUInt());
59       return true;
60     default:
61       return MessageModelItem::setData(column, value, role);
62   }
63 }
64
65 QVariant ChatLineModelItem::data(int column, int role) const {
66   if(role == ChatLineModel::MsgLabelRole)
67     return messageLabel();
68
69   QVariant variant;
70   MessageModel::ColumnType col = (MessageModel::ColumnType)column;
71   switch(col) {
72   case ChatLineModel::TimestampColumn:
73     variant = timestampData(role);
74     break;
75   case ChatLineModel::SenderColumn:
76     variant = senderData(role);
77     break;
78   case ChatLineModel::ContentsColumn:
79     variant = contentsData(role);
80     break;
81   default:
82     break;
83   }
84   if(!variant.isValid())
85     return MessageModelItem::data(column, role);
86   return variant;
87 }
88
89 QVariant ChatLineModelItem::timestampData(int role) const {
90   switch(role) {
91   case ChatLineModel::DisplayRole:
92     return _styledMsg.decoratedTimestamp();
93   case ChatLineModel::EditRole:
94     return _styledMsg.timestamp();
95   case ChatLineModel::BackgroundRole:
96     return backgroundBrush(UiStyle::Timestamp);
97   case ChatLineModel::SelectedBackgroundRole:
98     return backgroundBrush(UiStyle::Timestamp, true);
99   case ChatLineModel::FormatRole:
100     return QVariant::fromValue<UiStyle::FormatList>(UiStyle::FormatList()
101                       << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Timestamp));
102   }
103   return QVariant();
104 }
105
106 QVariant ChatLineModelItem::senderData(int role) const {
107   switch(role) {
108   case ChatLineModel::DisplayRole:
109     return _styledMsg.decoratedSender();
110   case ChatLineModel::EditRole:
111     return _styledMsg.plainSender();
112   case ChatLineModel::BackgroundRole:
113     return backgroundBrush(UiStyle::Sender);
114   case ChatLineModel::SelectedBackgroundRole:
115     return backgroundBrush(UiStyle::Sender, true);
116   case ChatLineModel::FormatRole:
117     return QVariant::fromValue<UiStyle::FormatList>(UiStyle::FormatList()
118                       << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Sender));
119   }
120   return QVariant();
121 }
122
123 QVariant ChatLineModelItem::contentsData(int role) const {
124   switch(role) {
125   case ChatLineModel::DisplayRole:
126   case ChatLineModel::EditRole:
127     return _styledMsg.plainContents();
128   case ChatLineModel::BackgroundRole:
129     return backgroundBrush(UiStyle::Contents);
130   case ChatLineModel::SelectedBackgroundRole:
131     return backgroundBrush(UiStyle::Contents, true);
132   case ChatLineModel::FormatRole:
133     return QVariant::fromValue<UiStyle::FormatList>(_styledMsg.contentsFormatList());
134   case ChatLineModel::WrapListRole:
135     if(_wrapList.isEmpty())
136       computeWrapList();
137     return QVariant::fromValue<ChatLineModel::WrapList>(_wrapList);
138   }
139   return QVariant();
140 }
141
142 quint32 ChatLineModelItem::messageLabel() const {
143   quint32 label = _styledMsg.senderHash() << 16;
144   if(_styledMsg.flags() & Message::Self)
145     label |= UiStyle::OwnMsg;
146   if(_styledMsg.flags() & Message::Highlight)
147     label |= UiStyle::Highlight;
148   return label;
149 }
150
151 QVariant ChatLineModelItem::backgroundBrush(UiStyle::FormatType subelement, bool selected) const {
152   QTextCharFormat fmt = QtUi::style()->format(UiStyle::formatType(_styledMsg.type()) | subelement, messageLabel() | (selected ? UiStyle::Selected : 0));
153   if(fmt.hasProperty(QTextFormat::BackgroundBrush))
154     return QVariant::fromValue<QBrush>(fmt.background());
155   return QVariant();
156 }
157
158 void ChatLineModelItem::computeWrapList() const {
159   QString text = _styledMsg.plainContents();
160   int length = text.length();
161   if(!length)
162     return;
163
164   QList<ChatLineModel::Word> wplist;  // use a temp list which we'll later copy into a QVector for efficiency
165   QTextBoundaryFinder finder(QTextBoundaryFinder::Line, _styledMsg.plainContents().unicode(), length,
166                               TextBoundaryFinderBuffer, TextBoundaryFinderBufferSize);
167
168   int idx;
169   int oldidx = 0;
170   ChatLineModel::Word word;
171   word.start = 0;
172   qreal wordstartx = 0;
173
174   QTextLayout layout(_styledMsg.plainContents());
175   QTextOption option;
176   option.setWrapMode(QTextOption::NoWrap);
177   layout.setTextOption(option);
178
179   layout.setAdditionalFormats(QtUi::style()->toTextLayoutList(_styledMsg.contentsFormatList(), length, messageLabel()));
180   layout.beginLayout();
181   QTextLine line = layout.createLine();
182   line.setNumColumns(length);
183   layout.endLayout();
184
185   while((idx = finder.toNextBoundary()) >= 0 && idx <= length) {
186     if(idx < length)
187       idx++;  // the boundary is *before* the actual character
188
189     if(idx == oldidx)
190       continue;
191
192     word.start = oldidx;
193     int wordend = idx;
194     for(; wordend > word.start; wordend--) {
195       if(!text.at(wordend-1).isSpace())
196         break;
197     }
198
199     qreal wordendx = line.cursorToX(wordend);
200     qreal trailingendx = line.cursorToX(idx);
201     word.endX = wordendx;
202     word.width = wordendx - wordstartx;
203     word.trailing = trailingendx - wordendx;
204     wordstartx = trailingendx;
205     wplist.append(word);
206
207     oldidx = idx;
208   }
209
210   // A QVector needs less space than a QList
211   _wrapList.resize(wplist.count());
212   for(int i = 0; i < wplist.count(); i++) {
213     _wrapList[i] = wplist.at(i);
214   }
215 }
216