Wupps, Altlast-Alarm
[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   int length = _styledMsg.plainContents().length();
160   if(!length)
161     return;
162
163   enum Mode { SearchStart, SearchEnd };
164
165   QList<ChatLineModel::Word> wplist;  // use a temp list which we'll later copy into a QVector for efficiency
166   QTextBoundaryFinder finder(QTextBoundaryFinder::Word, _styledMsg.plainContents().unicode(), length,
167                               TextBoundaryFinderBuffer, TextBoundaryFinderBufferSize);
168
169   int idx;
170   int oldidx = 0;
171   bool wordStart = false;
172   bool wordEnd = false;
173   Mode mode = SearchEnd;
174   ChatLineModel::Word word;
175   word.start = 0;
176   qreal wordstartx = 0;
177
178   QTextLayout layout(_styledMsg.plainContents());
179   QTextOption option;
180   option.setWrapMode(QTextOption::NoWrap);
181   layout.setTextOption(option);
182
183   layout.setAdditionalFormats(QtUi::style()->toTextLayoutList(_styledMsg.contentsFormatList(), length, messageLabel()));
184   layout.beginLayout();
185   QTextLine line = layout.createLine();
186   line.setNumColumns(length);
187   layout.endLayout();
188
189   do {
190     idx = finder.toNextBoundary();
191     if(idx < 0) {
192       idx = length;
193       wordStart = false;
194       wordEnd = false;
195       mode = SearchStart;
196     } else {
197       wordStart = finder.boundaryReasons().testFlag(QTextBoundaryFinder::StartWord);
198       wordEnd = finder.boundaryReasons().testFlag(QTextBoundaryFinder::EndWord);
199     }
200
201     //if(flg) qDebug() << idx << mode << wordStart << wordEnd << contents->plainText.left(idx) << contents->plainText.mid(idx);
202
203     if(mode == SearchEnd || (!wordStart && wordEnd)) {
204       if(wordStart || !wordEnd) continue;
205       oldidx = idx;
206       mode = SearchStart;
207       continue;
208     }
209     qreal wordendx = line.cursorToX(oldidx);
210     qreal trailingendx = line.cursorToX(idx);
211     word.endX = wordendx;
212     word.width = wordendx - wordstartx;
213     word.trailing = trailingendx - wordendx;
214     wordstartx = trailingendx;
215     wplist.append(word);
216
217     if(wordStart) {
218       word.start = idx;
219       mode = SearchEnd;
220     }
221     // the part " || (finder.position() == contents->plainText.length())" shouldn't be necessary
222     // but in rare and indeterministic cases Qt states that the end of the text is not a boundary o_O
223   } while(finder.isAtBoundary() || (finder.position() == length));
224
225   // A QVector needs less space than a QList
226   _wrapList.resize(wplist.count());
227   for(int i = 0; i < wplist.count(); i++) {
228     _wrapList[i] = wplist.at(i);
229   }
230 }
231