1 /* This file is part of the KDE project
2 * Copyright (C) 2009 Pierre Stirnweiss <pstirnweiss@googlemail.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #include "TrackedChangeModel.h"
21
22 #include <KoTextDocument.h>
23 #include <KoTextDocumentLayout.h>
24 #include <KoGenChange.h>
25 #include <KoInlineTextObjectManager.h>
26 #include <changetracker/KoChangeTracker.h>
27 #include <changetracker/KoChangeTrackerElement.h>
28 #include <styles/KoCharacterStyle.h>
29
30 #include <kundo2magicstring.h>
31
32 #include <QHash>
33 #include <QModelIndex>
34 #include <QStack>
35 #include <QTextBlock>
36 #include <QTextCharFormat>
37 #include <QTextCursor>
38 #include <QTextDocument>
39 #include <QTextFragment>
40
41 ////ModelItem
42
ModelItem(ModelItem * parent)43 ModelItem::ModelItem(ModelItem *parent)
44 {
45 m_parentItem = parent;
46 m_data.changeId = 0;
47 }
48
~ModelItem()49 ModelItem::~ModelItem()
50 {
51 qDeleteAll(m_childItems);
52 }
53
setChangeId(int changeId)54 void ModelItem::setChangeId(int changeId)
55 {
56 m_data.changeId = changeId;
57 }
58
setChangeType(KoGenChange::Type type)59 void ModelItem::setChangeType(KoGenChange::Type type)
60 {
61 m_data.changeType = type;
62 }
63
setChangeTitle(const QString & title)64 void ModelItem::setChangeTitle(const QString &title)
65 {
66 m_data.title = title;
67 }
68
setChangeAuthor(const QString & author)69 void ModelItem::setChangeAuthor(const QString &author)
70 {
71 m_data.author = author;
72 }
73
appendChild(ModelItem * child)74 void ModelItem::appendChild(ModelItem *child)
75 {
76 m_childItems.append(child);
77 }
78
child(int row)79 ModelItem *ModelItem::child(int row)
80 {
81 return m_childItems.value(row);
82 }
83
children()84 QList< ModelItem * > ModelItem::children()
85 {
86 return m_childItems;
87 }
88
childCount() const89 int ModelItem::childCount() const
90 {
91 return m_childItems.count();
92 }
93
parent()94 ModelItem *ModelItem::parent()
95 {
96 return m_parentItem;
97 }
98
row() const99 int ModelItem::row() const
100 {
101 if (m_parentItem) {
102 return m_parentItem->m_childItems.indexOf(const_cast<ModelItem *>(this));
103 }
104 return 0;
105 }
106
setChangeRange(int start,int end)107 void ModelItem::setChangeRange(int start, int end)
108 {
109 m_data.changeRanges.append(QPair<int, int>(start, end));
110 }
111
itemData()112 ItemData ModelItem::itemData()
113 {
114 return m_data;
115 }
116
removeChildren()117 void ModelItem::removeChildren()
118 {
119 qDeleteAll(m_childItems);
120 m_childItems.clear();
121 }
122
123 ////TrackedChangeModel
124
TrackedChangeModel(QTextDocument * document,QObject * parent)125 TrackedChangeModel::TrackedChangeModel(QTextDocument *document, QObject *parent)
126 : QAbstractItemModel(parent)
127 , m_document(document)
128 {
129 m_rootItem = new ModelItem(0);
130 setupModelData(m_document, m_rootItem);
131 }
132
~TrackedChangeModel()133 TrackedChangeModel::~TrackedChangeModel()
134 {
135 delete m_rootItem;
136 }
137
index(int row,int column,const QModelIndex & parent) const138 QModelIndex TrackedChangeModel::index(int row, int column, const QModelIndex &parent) const
139 {
140 if (!hasIndex(row, column, parent)) {
141 return QModelIndex();
142 }
143
144 ModelItem *parentItem;
145
146 if (!parent.isValid()) {
147 parentItem = m_rootItem;
148 } else {
149 parentItem = static_cast<ModelItem *>(parent.internalPointer());
150 }
151
152 ModelItem *childItem = parentItem->child(row);
153 if (childItem) {
154 return createIndex(row, column, childItem);
155 } else {
156 return QModelIndex();
157 }
158 }
159
indexForChangeId(int changeId)160 QModelIndex TrackedChangeModel::indexForChangeId(int changeId)
161 {
162 ModelItem *item = m_changeItems.value(changeId);
163 if (!item) {
164 return QModelIndex();
165 }
166 return createIndex(item->row(), 0, item);
167 }
168
parent(const QModelIndex & index) const169 QModelIndex TrackedChangeModel::parent(const QModelIndex &index) const
170 {
171 if (!index.isValid()) {
172 return QModelIndex();
173 }
174
175 ModelItem *childItem = static_cast<ModelItem *>(index.internalPointer());
176 ModelItem *parentItem = childItem->parent();
177
178 if (parentItem == m_rootItem) {
179 return QModelIndex();
180 }
181
182 return createIndex(parentItem->row(), 0, parentItem);
183 }
184
rowCount(const QModelIndex & parent) const185 int TrackedChangeModel::rowCount(const QModelIndex &parent) const
186 {
187 ModelItem *parentItem;
188 if (parent.column() > 0) {
189 return 0;
190 }
191
192 if (!parent.isValid()) {
193 parentItem = m_rootItem;
194 } else {
195 parentItem = static_cast<ModelItem *>(parent.internalPointer());
196 }
197
198 return parentItem->childCount();
199 }
200
columnCount(const QModelIndex & parent) const201 int TrackedChangeModel::columnCount(const QModelIndex &parent) const
202 {
203 Q_UNUSED(parent);
204 return 3;
205 }
206
changeItemData(const QModelIndex & index,int role) const207 ItemData TrackedChangeModel::changeItemData(const QModelIndex &index, int role) const
208 {
209 if (!index.isValid()) {
210 return ItemData();
211 }
212
213 if (role != Qt::DisplayRole) {
214 return ItemData();
215 }
216
217 ModelItem *item = static_cast<ModelItem *>(index.internalPointer());
218
219 return item->itemData();
220 }
221
data(const QModelIndex & index,int role) const222 QVariant TrackedChangeModel::data(const QModelIndex &index, int role) const
223 {
224 if (!index.isValid()) {
225 return QVariant();
226 }
227
228 if (role != Qt::DisplayRole) {
229 return QVariant();
230 }
231
232 ModelItem *item = static_cast<ModelItem *>(index.internalPointer());
233
234 ItemData data = item->itemData();
235
236 switch (index.column()) {
237 case 0:
238 return QVariant(item->itemData().changeId);
239 case 1:
240 return QVariant(item->itemData().title);
241 case 2:
242 return QVariant(item->itemData().author);
243 default:
244 break;
245 }
246 return QVariant();
247 }
248
flags(const QModelIndex & index) const249 Qt::ItemFlags TrackedChangeModel::flags(const QModelIndex &index) const
250 {
251 if (!index.isValid()) {
252 return 0;
253 }
254
255 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
256 }
257
headerData(int section,Qt::Orientation orientation,int role) const258 QVariant TrackedChangeModel::headerData(int section, Qt::Orientation orientation, int role) const
259 {
260 Q_UNUSED(section);
261
262 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
263 switch (section) {
264 case 0:
265 return QVariant(QString("changeId"));
266 break;
267 case 1:
268 return QVariant(QString("title"));
269 break;
270 case 2:
271 return QVariant(QString("author"));
272 break;
273 }
274 }
275
276 return QVariant();
277 }
278
setupModel()279 void TrackedChangeModel::setupModel()
280 {
281 beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
282 m_rootItem->removeChildren();
283 endRemoveRows();
284 setupModelData(m_document, m_rootItem);
285 beginInsertRows(QModelIndex(), 0, m_rootItem->childCount() - 1);
286 endInsertRows();
287 }
288
setupModelData(QTextDocument * document,ModelItem * parent)289 void TrackedChangeModel::setupModelData(QTextDocument *document, ModelItem *parent)
290 {
291 m_changeTracker = KoTextDocument(document).changeTracker();
292 m_layout = dynamic_cast<KoTextDocumentLayout *>(document->documentLayout());
293
294 QStack<ModelItem *> itemStack;
295 itemStack.push(parent);
296 m_changeItems.clear();
297
298 QTextBlock block = document->begin();
299 while (block.isValid()) {
300 QTextBlock::iterator it;
301 for (it = block.begin(); !(it.atEnd()); ++it) {
302 QTextFragment fragment = it.fragment();
303 QTextCharFormat format = fragment.charFormat();
304 int changeId = format.property(KoCharacterStyle::ChangeTrackerId).toInt();
305 // if (m_changeTracker->elementById(changeId) && m_changeTracker->elementById(changeId)->getChangeType() == KoGenChange::deleteChange)
306 // continue;
307 if (changeId) {
308 if (changeId != itemStack.top()->itemData().changeId) {
309 while (itemStack.top() != parent) {
310 if (!m_changeTracker->isParent(itemStack.top()->itemData().changeId, changeId)) {
311 itemStack.pop();
312 } else {
313 break;
314 }
315 }
316 }
317 ModelItem *item = m_changeItems.value(changeId);
318 if (!item) {
319 item = new ModelItem(itemStack.top());
320 item->setChangeId(changeId);
321 item->setChangeType(m_changeTracker->elementById(changeId)->getChangeType());
322 item->setChangeTitle(m_changeTracker->elementById(changeId)->getChangeTitle().toString());
323 item->setChangeAuthor(m_changeTracker->elementById(changeId)->getCreator());
324 itemStack.top()->appendChild(item);
325 m_changeItems.insert(changeId, item);
326 }
327 item->setChangeRange(fragment.position(), fragment.position() + fragment.length());
328 ModelItem *parentItem = item->parent();
329 while (parentItem->itemData().changeId) {
330 parentItem->setChangeRange(fragment.position(), fragment.position() + fragment.length());
331 parentItem = parentItem->parent();
332 }
333 itemStack.push(item);
334
335 } else {
336 itemStack.push(parent);
337 }
338 }
339 block = block.next();
340 }
341 }
342