Quassel IRC  Pre-Release
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
chatlinemodelitem.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * Copyright (C) 2005-2015 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 #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;
39 
40 unsigned char *ChatLineModelItem::TextBoundaryFinderBuffer = (unsigned char *)malloc(512 * sizeof(HB_CharAttributes_Dummy));
41 int ChatLineModelItem::TextBoundaryFinderBufferSize = 512 * (sizeof(HB_CharAttributes_Dummy) / sizeof(unsigned char));
42 
43 // ****************************************
44 // the actual ChatLineModelItem
45 // ****************************************
47  : MessageModelItem(),
48  _styledMsg(msg)
49 {
50  if (!msg.sender().contains('!'))
51  _styledMsg.setFlags(msg.flags() |= Message::ServerMsg);
52 }
53 
54 
55 bool ChatLineModelItem::setData(int column, const QVariant &value, int role)
56 {
57  switch (role) {
59  _styledMsg.setFlags((Message::Flags)value.toUInt());
60  return true;
61  default:
62  return MessageModelItem::setData(column, value, role);
63  }
64 }
65 
66 
67 QVariant ChatLineModelItem::data(int column, int role) const
68 {
69  if (role == ChatLineModel::MsgLabelRole)
70  return messageLabel();
71 
72  QVariant variant;
74  switch (col) {
76  variant = timestampData(role);
77  break;
79  variant = senderData(role);
80  break;
82  variant = contentsData(role);
83  break;
84  default:
85  break;
86  }
87  if (!variant.isValid())
88  return MessageModelItem::data(column, role);
89  return variant;
90 }
91 
92 
93 QVariant ChatLineModelItem::timestampData(int role) const
94 {
95  switch (role) {
99  return _styledMsg.timestamp();
103  return backgroundBrush(UiStyle::Timestamp, true);
105  return QVariant::fromValue<UiStyle::FormatList>(UiStyle::FormatList()
106  << qMakePair((quint16)0, (quint32) UiStyle::formatType(_styledMsg.type()) | UiStyle::Timestamp));
107  }
108  return QVariant();
109 }
110 
111 
112 QVariant ChatLineModelItem::senderData(int role) const
113 {
114  switch (role) {
116  return _styledMsg.decoratedSender();
118  return _styledMsg.plainSender();
122  return backgroundBrush(UiStyle::Sender, true);
124  return QVariant::fromValue<UiStyle::FormatList>(UiStyle::FormatList()
125  << qMakePair((quint16)0, (quint32) UiStyle::formatType(_styledMsg.type()) | UiStyle::Sender));
126  }
127  return QVariant();
128 }
129 
130 
131 QVariant ChatLineModelItem::contentsData(int role) const
132 {
133  switch (role) {
136  return _styledMsg.plainContents();
140  return backgroundBrush(UiStyle::Contents, true);
142  return QVariant::fromValue<UiStyle::FormatList>(_styledMsg.contentsFormatList());
144  if (_wrapList.isEmpty())
145  computeWrapList();
146  return QVariant::fromValue<ChatLineModel::WrapList>(_wrapList);
147  }
148  return QVariant();
149 }
150 
151 
153 {
154  quint32 label = _styledMsg.senderHash() << 16;
155  if (_styledMsg.flags() & Message::Self)
156  label |= UiStyle::OwnMsg;
157  if (_styledMsg.flags() & Message::Highlight)
158  label |= UiStyle::Highlight;
159  return label;
160 }
161 
162 
163 QVariant ChatLineModelItem::backgroundBrush(UiStyle::FormatType subelement, bool selected) const
164 {
165  QTextCharFormat fmt = QtUi::style()->format(UiStyle::formatType(_styledMsg.type()) | subelement, messageLabel() | (selected ? UiStyle::Selected : 0));
166  if (fmt.hasProperty(QTextFormat::BackgroundBrush))
167  return QVariant::fromValue<QBrush>(fmt.background());
168  return QVariant();
169 }
170 
171 
173 {
174  QString text = _styledMsg.plainContents();
175  int length = text.length();
176  if (!length)
177  return;
178 
179  QList<ChatLineModel::Word> wplist; // use a temp list which we'll later copy into a QVector for efficiency
180  QTextBoundaryFinder finder(QTextBoundaryFinder::Line, _styledMsg.plainContents().unicode(), length,
182 
183  int idx;
184  int oldidx = 0;
185  ChatLineModel::Word word;
186  word.start = 0;
187  qreal wordstartx = 0;
188 
189  QTextLayout layout(_styledMsg.plainContents());
190  QTextOption option;
191  option.setWrapMode(QTextOption::NoWrap);
192  layout.setTextOption(option);
193 
194  layout.setAdditionalFormats(QtUi::style()->toTextLayoutList(_styledMsg.contentsFormatList(), length, messageLabel()));
195  layout.beginLayout();
196  QTextLine line = layout.createLine();
197  line.setNumColumns(length);
198  layout.endLayout();
199 
200  while ((idx = finder.toNextBoundary()) >= 0 && idx <= length) {
201  // QTextBoundaryFinder has inconsistent behavior in Qt version up to and including 4.6.3 (at least).
202  // It doesn't point to the position we should break, but to the character before that.
203  // Unfortunately Qt decided to fix this by changing the behavior of QTBF, so now we have to add a version
204  // check. At the time of this writing, I'm still trying to get this reverted upstream...
205  //
206  // cf. https://bugs.webkit.org/show_bug.cgi?id=31076 and Qt commit e6ac173
207  static int needWorkaround = -1;
208  if (needWorkaround < 0) {
209  needWorkaround = 0;
210  QStringList versions = QString(qVersion()).split('.');
211  if (versions.count() == 3 && versions.at(0).toInt() == 4) {
212  if (versions.at(1).toInt() <= 6 && versions.at(2).toInt() <= 3)
213  needWorkaround = 1;
214  }
215  }
216  if (needWorkaround == 1) {
217  if (idx < length)
218  idx++;
219  }
220 
221  if (idx == oldidx)
222  continue;
223 
224  word.start = oldidx;
225  int wordend = idx;
226  for (; wordend > word.start; wordend--) {
227  if (!text.at(wordend-1).isSpace())
228  break;
229  }
230 
231  qreal wordendx = line.cursorToX(wordend);
232  qreal trailingendx = line.cursorToX(idx);
233  word.endX = wordendx;
234  word.width = wordendx - wordstartx;
235  word.trailing = trailingendx - wordendx;
236  wordstartx = trailingendx;
237  wplist.append(word);
238 
239  oldidx = idx;
240  }
241 
242  // A QVector needs less space than a QList
243  _wrapList.resize(wplist.count());
244  for (int i = 0; i < wplist.count(); i++) {
245  _wrapList[i] = wplist.at(i);
246  }
247 }