0bcc7a785a9b4c5d991e6e0f3a0df882f91111ad
[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 QVariant ChatLineModelItem::data(int column, int role) const {
56   if(role == ChatLineModel::MsgLabelRole)
57     return messageLabel();
58
59   QVariant variant;
60   MessageModel::ColumnType col = (MessageModel::ColumnType)column;
61   switch(col) {
62   case ChatLineModel::TimestampColumn:
63     variant = timestampData(role);
64     break;
65   case ChatLineModel::SenderColumn:
66     variant = senderData(role);
67     break;
68   case ChatLineModel::ContentsColumn:
69     variant = contentsData(role);
70     break;
71   default:
72     break;
73   }
74   if(!variant.isValid())
75     return MessageModelItem::data(column, role);
76   return variant;
77 }
78
79 QVariant ChatLineModelItem::timestampData(int role) const {
80   switch(role) {
81   case ChatLineModel::DisplayRole:
82     return _styledMsg.decoratedTimestamp();
83   case ChatLineModel::EditRole:
84     return _styledMsg.timestamp();
85   case ChatLineModel::BackgroundRole:
86     return backgroundBrush(UiStyle::Timestamp);
87   case ChatLineModel::FormatRole:
88     return QVariant::fromValue<UiStyle::FormatList>(UiStyle::FormatList()
89                       << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Timestamp));
90   }
91   return QVariant();
92 }
93
94 QVariant ChatLineModelItem::senderData(int role) const {
95   switch(role) {
96   case ChatLineModel::DisplayRole:
97     return _styledMsg.decoratedSender();
98   case ChatLineModel::EditRole:
99     return _styledMsg.plainSender();
100   case ChatLineModel::BackgroundRole:
101     return backgroundBrush(UiStyle::Sender);
102   case ChatLineModel::FormatRole:
103     return QVariant::fromValue<UiStyle::FormatList>(UiStyle::FormatList()
104                       << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Sender));
105   }
106   return QVariant();
107 }
108
109 QVariant ChatLineModelItem::contentsData(int role) const {
110   switch(role) {
111   case ChatLineModel::DisplayRole:
112   case ChatLineModel::EditRole:
113     return _styledMsg.plainContents();
114   case ChatLineModel::BackgroundRole:
115     return backgroundBrush(UiStyle::Contents);
116   case ChatLineModel::FormatRole:
117     return QVariant::fromValue<UiStyle::FormatList>(_styledMsg.contentsFormatList());
118   case ChatLineModel::WrapListRole:
119     if(_wrapList.isEmpty())
120       computeWrapList();
121     return QVariant::fromValue<ChatLineModel::WrapList>(_wrapList);
122   }
123   return QVariant();
124 }
125
126 quint32 ChatLineModelItem::messageLabel() const {
127   quint32 label = 0;
128   if(_styledMsg.flags() & Message::Self)
129     label |= UiStyle::OwnMsg;
130   if(_styledMsg.flags() & Message::Highlight)
131     label |= UiStyle::Highlight;
132   return label;
133 }
134
135 QVariant ChatLineModelItem::backgroundBrush(UiStyle::FormatType subelement) const {
136   QTextCharFormat fmt = QtUi::style()->format(UiStyle::formatType(_styledMsg.type()) | subelement, messageLabel());
137   if(fmt.hasProperty(QTextFormat::BackgroundBrush))
138     return QVariant::fromValue<QBrush>(fmt.background());
139   return QVariant();
140 }
141
142 void ChatLineModelItem::computeWrapList() const {
143   int length = _styledMsg.plainContents().length();
144   if(!length)
145     return;
146
147   enum Mode { SearchStart, SearchEnd };
148
149   QList<ChatLineModel::Word> wplist;  // use a temp list which we'll later copy into a QVector for efficiency
150   QTextBoundaryFinder finder(QTextBoundaryFinder::Word, _styledMsg.plainContents().unicode(), length,
151                               TextBoundaryFinderBuffer, TextBoundaryFinderBufferSize);
152
153   int idx;
154   int oldidx = 0;
155   bool wordStart = false;
156   bool wordEnd = false;
157   Mode mode = SearchEnd;
158   ChatLineModel::Word word;
159   word.start = 0;
160   qreal wordstartx = 0;
161
162   QTextLayout layout(_styledMsg.plainContents());
163   QTextOption option;
164   option.setWrapMode(QTextOption::NoWrap);
165   layout.setTextOption(option);
166
167   layout.setAdditionalFormats(QtUi::style()->toTextLayoutList(_styledMsg.contentsFormatList(), length, messageLabel()));
168   layout.beginLayout();
169   QTextLine line = layout.createLine();
170   line.setNumColumns(length);
171   layout.endLayout();
172
173   do {
174     idx = finder.toNextBoundary();
175     if(idx < 0) {
176       idx = length;
177       wordStart = false;
178       wordEnd = false;
179       mode = SearchStart;
180     } else {
181       wordStart = finder.boundaryReasons().testFlag(QTextBoundaryFinder::StartWord);
182       wordEnd = finder.boundaryReasons().testFlag(QTextBoundaryFinder::EndWord);
183     }
184
185     //if(flg) qDebug() << idx << mode << wordStart << wordEnd << contents->plainText.left(idx) << contents->plainText.mid(idx);
186
187     if(mode == SearchEnd || (!wordStart && wordEnd)) {
188       if(wordStart || !wordEnd) continue;
189       oldidx = idx;
190       mode = SearchStart;
191       continue;
192     }
193     qreal wordendx = line.cursorToX(oldidx);
194     qreal trailingendx = line.cursorToX(idx);
195     word.endX = wordendx;
196     word.width = wordendx - wordstartx;
197     word.trailing = trailingendx - wordendx;
198     wordstartx = trailingendx;
199     wplist.append(word);
200
201     if(wordStart) {
202       word.start = idx;
203       mode = SearchEnd;
204     }
205     // the part " || (finder.position() == contents->plainText.length())" shouldn't be necessary
206     // but in rare and indeterministic cases Qt states that the end of the text is not a boundary o_O
207   } while(finder.isAtBoundary() || (finder.position() == length));
208
209   // A QVector needs less space than a QList
210   _wrapList.resize(wplist.count());
211   for(int i = 0; i < wplist.count(); i++) {
212     _wrapList[i] = wplist.at(i);
213   }
214 }
215