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