1 /***************************************************************************
2 * Copyright (C) 2005-08 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
21 #include <QFontMetrics>
22 #include <QTextBoundaryFinder>
24 #include "chatlinemodelitem.h"
25 #include "chatlinemodel.h"
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
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;
38 } HB_CharAttributes_Dummy;
40 // PRIVATE DATA FOR CHATLINE MODEL ITEM
41 class ChatLineModelItemPrivate {
44 UiStyle::FormatList formatList;
45 inline ChatLinePart(const QString &pT, const UiStyle::FormatList &fL) : plainText(pT), formatList(fL) {}
49 inline ChatLineModelItemPrivate(const Message &msg) : _msgBuffer(new Message(msg)), timestamp(0), sender(0), contents(0) {}
50 inline ~ChatLineModelItemPrivate() {
60 inline bool needsStyling() { return (bool)_msgBuffer; }
62 inline ChatLinePart *partByColumn(MessageModel::ColumnType column) {
64 case ChatLineModel::TimestampColumn:
66 case ChatLineModel::SenderColumn:
68 case ChatLineModel::ContentsColumn:
76 inline const QString &plainText(MessageModel::ColumnType column) {
79 return partByColumn(column)->plainText;
82 inline const UiStyle::FormatList &formatList(MessageModel::ColumnType column) {
85 return partByColumn(column)->formatList;
88 inline const ChatLineModel::WrapList &wrapList() {
91 if(_wrapList.isEmpty())
98 QtUiStyle::StyledMessage m = QtUi::style()->styleMessage(*_msgBuffer);
100 timestamp = new ChatLinePart(m.timestamp.plainText, m.timestamp.formatList);
101 sender = new ChatLinePart(m.sender.plainText, m.sender.formatList);
102 contents = new ChatLinePart(m.contents.plainText, m.contents.formatList);
108 inline void computeWrapList() {
109 if(contents->plainText.isEmpty())
112 enum Mode { SearchStart, SearchEnd };
114 QList<ChatLineModel::Word> wplist; // use a temp list which we'll later copy into a QVector for efficiency
115 QTextBoundaryFinder finder(QTextBoundaryFinder::Word, contents->plainText.unicode(), contents->plainText.length(), TextBoundaryFinderBuffer, TextBoundaryFinderBufferSize);
119 bool wordStart = false;
120 bool wordEnd = false;
121 Mode mode = SearchEnd;
122 ChatLineModel::Word word;
124 qreal wordstartx = 0;
126 QTextLayout layout(contents->plainText);
128 option.setWrapMode(QTextOption::NoWrap);
129 layout.setTextOption(option);
131 layout.setAdditionalFormats(QtUi::style()->toTextLayoutList(contents->formatList, contents->plainText.length()));
132 layout.beginLayout();
133 QTextLine line = layout.createLine();
134 line.setNumColumns(contents->plainText.length());
138 idx = finder.toNextBoundary();
140 idx = contents->plainText.length();
145 wordStart = finder.boundaryReasons().testFlag(QTextBoundaryFinder::StartWord);
146 wordEnd = finder.boundaryReasons().testFlag(QTextBoundaryFinder::EndWord);
149 //if(flg) qDebug() << idx << mode << wordStart << wordEnd << contents->plainText.left(idx) << contents->plainText.mid(idx);
151 if(mode == SearchEnd || (!wordStart && wordEnd)) {
152 if(wordStart || !wordEnd) continue;
157 qreal wordendx = line.cursorToX(oldidx);
158 qreal trailingendx = line.cursorToX(idx);
159 word.width = wordendx - wordstartx;
160 word.trailing = trailingendx - wordendx;
161 wordstartx = trailingendx;
168 // the part " || (finder.position() == contents->plainText.length())" shouldn't be necessary
169 // but in rare and indeterministic cases Qt states that the end of the text is not a boundary o_O
170 } while(finder.isAtBoundary() || (finder.position() == contents->plainText.length()));
172 // A QVector needs less space than a QList
173 _wrapList.resize(wplist.count());
174 for(int i = 0; i < wplist.count(); i++) {
175 _wrapList[i] = wplist.at(i);
179 ChatLineModel::WrapList _wrapList;
181 ChatLinePart *timestamp, *sender, *contents;
183 static unsigned char *TextBoundaryFinderBuffer;
184 static int TextBoundaryFinderBufferSize;
187 unsigned char *ChatLineModelItemPrivate::TextBoundaryFinderBuffer = (unsigned char *)malloc(512 * sizeof(HB_CharAttributes_Dummy));
188 int ChatLineModelItemPrivate::TextBoundaryFinderBufferSize = 512 * (sizeof(HB_CharAttributes_Dummy) / sizeof(unsigned char));
191 // ****************************************
192 // the actual ChatLineModelItem
193 // ****************************************
194 ChatLineModelItem::ChatLineModelItem(const Message &msg)
195 : MessageModelItem(msg),
196 _data(new ChatLineModelItemPrivate(msg))
200 ChatLineModelItem::~ChatLineModelItem() {
204 QVariant ChatLineModelItem::data(int column, int role) const {
205 if(column < ChatLineModel::TimestampColumn || column > ChatLineModel::ContentsColumn)
206 return MessageModelItem::data(column, role);
207 MessageModel::ColumnType columnType = (MessageModel::ColumnType)column;
210 case ChatLineModel::DisplayRole:
211 return _data->plainText(columnType);
212 case ChatLineModel::FormatRole:
213 return QVariant::fromValue<UiStyle::FormatList>(_data->formatList(columnType));
214 case ChatLineModel::WrapListRole:
215 if(columnType != ChatLineModel::ContentsColumn)
217 return QVariant::fromValue<ChatLineModel::WrapList>(_data->wrapList());
219 return MessageModelItem::data(column, role);