1 /* This file is part of the KDE project
2  * Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org>
3  * Copyright (C) 2011 Boudewijn Rempt <boud@kogmbh.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library 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 GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 #include "KoChangeTracker.h"
22 
23 //Calligra includes
24 #include "styles/KoCharacterStyle.h"
25 #include "KoChangeTrackerElement.h"
26 #include <KoXmlReader.h>
27 #include <KoXmlNS.h>
28 #include <KoInlineTextObjectManager.h>
29 #include <KoTextDocument.h>
30 #include <KoList.h>
31 #include <KoListStyle.h>
32 #include <KoParagraphStyle.h>
33 #include <KoGenChanges.h>
34 #include <KoFormatChangeInformation.h>
35 #include <kundo2magicstring.h>
36 #include "TextDebug.h"
37 
38 // KF5
39 #include <klocalizedstring.h>
40 
41 //Qt includes
42 #include <QColor>
43 #include <QList>
44 #include <QString>
45 #include <QHash>
46 #include <QMultiHash>
47 #include <QTextCursor>
48 #include <QTextFormat>
49 #include <QTextDocument>
50 #include <QTextDocumentFragment>
51 #include <QTextList>
52 #include <QTextTable>
53 #include <QDateTime>
54 #include <QLocale>
55 
56 class Q_DECL_HIDDEN KoChangeTracker::Private
57 {
58 public:
Private()59     Private()
60       : changeId(1),
61         recordChanges(false),
62         displayChanges(false),
63         insertionBgColor(101,255,137),
64         deletionBgColor(255,185,185),
65         formatChangeBgColor(195,195,255),
66         changeSaveFormat(UNKNOWN)
67 
68     {
69     }
~Private()70     ~Private() { }
71 
72     QMultiHash<int, int> children;
73     QMultiHash<int, int> duplicateIds;
74     QHash<int, int> parents;
75     QHash<int, KoChangeTrackerElement *> changes;
76     QHash<QString, int> loadedChanges;
77     QHash<int, KoFormatChangeInformation *> changeInformation;
78     QList<int> saveChanges;
79     QList<int> acceptedRejectedChanges;
80     int changeId;
81     bool recordChanges;
82     bool displayChanges;
83     QColor insertionBgColor, deletionBgColor, formatChangeBgColor;
84     QString changeAuthorName;
85     KoChangeTracker::ChangeSaveFormat changeSaveFormat;
86 };
87 
KoChangeTracker(QObject * parent)88 KoChangeTracker::KoChangeTracker(QObject *parent)
89     : QObject(parent),
90     d(new Private())
91 {
92     d->changeId = 1;
93 }
94 
~KoChangeTracker()95 KoChangeTracker::~KoChangeTracker()
96 {
97     delete d;
98 }
99 
setRecordChanges(bool enabled)100 void KoChangeTracker::setRecordChanges(bool enabled)
101 {
102     d->recordChanges = enabled;
103 }
104 
recordChanges() const105 bool KoChangeTracker::recordChanges() const
106 {
107     return d->recordChanges;
108 }
109 
setDisplayChanges(bool enabled)110 void KoChangeTracker::setDisplayChanges(bool enabled)
111 {
112     d->displayChanges = enabled;
113 }
114 
displayChanges() const115 bool KoChangeTracker::displayChanges() const
116 {
117     return d->displayChanges;
118 }
119 
authorName() const120 QString KoChangeTracker::authorName() const
121 {
122     return d->changeAuthorName;
123 }
124 
setAuthorName(const QString & authorName)125 void KoChangeTracker::setAuthorName(const QString &authorName)
126 {
127     d->changeAuthorName = authorName;
128 }
129 
saveFormat() const130 KoChangeTracker::ChangeSaveFormat KoChangeTracker::saveFormat() const
131 {
132     return d->changeSaveFormat;
133 }
134 
setSaveFormat(ChangeSaveFormat saveFormat)135 void KoChangeTracker::setSaveFormat(ChangeSaveFormat saveFormat)
136 {
137     d->changeSaveFormat = saveFormat;
138 }
139 
getFormatChangeId(const KUndo2MagicString & title,const QTextFormat & format,const QTextFormat & prevFormat,int existingChangeId)140 int KoChangeTracker::getFormatChangeId(const KUndo2MagicString &title, const QTextFormat &format, const QTextFormat &prevFormat, int existingChangeId)
141 {
142     if ( existingChangeId ) {
143         d->children.insert(existingChangeId, d->changeId);
144         d->parents.insert(d->changeId, existingChangeId);
145     }
146 
147     KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::FormatChange);
148     changeElement->setChangeFormat(format);
149     changeElement->setPrevFormat(prevFormat);
150 
151     QLocale l;
152     changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString(".")));
153 
154     changeElement->setCreator(d->changeAuthorName);
155 
156     changeElement->setEnabled(d->recordChanges);
157 
158     d->changes.insert(d->changeId, changeElement);
159 
160     return d->changeId++;
161 }
162 
getInsertChangeId(const KUndo2MagicString & title,int existingChangeId)163 int KoChangeTracker::getInsertChangeId(const KUndo2MagicString &title, int existingChangeId)
164 {
165     if ( existingChangeId ) {
166         d->children.insert(existingChangeId, d->changeId);
167         d->parents.insert(d->changeId, existingChangeId);
168     }
169 
170     KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::InsertChange);
171 
172     QLocale l;
173     changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString(".")));
174 
175     changeElement->setCreator(d->changeAuthorName);
176 
177     changeElement->setEnabled(d->recordChanges);
178 
179     d->changes.insert(d->changeId, changeElement);
180 
181     return d->changeId++;
182 }
183 
getDeleteChangeId(const KUndo2MagicString & title,const QTextDocumentFragment & selection,int existingChangeId)184 int KoChangeTracker::getDeleteChangeId(const KUndo2MagicString &title, const QTextDocumentFragment &selection, int existingChangeId)
185 {
186     if ( existingChangeId ) {
187         d->children.insert(existingChangeId, d->changeId);
188         d->parents.insert(d->changeId, existingChangeId);
189     }
190 
191     KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::DeleteChange);
192 
193     QLocale l;
194     changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString(".")));
195 
196     changeElement->setCreator(d->changeAuthorName);
197     changeElement->setDeleteData(selection);
198 
199     changeElement->setEnabled(d->recordChanges);
200 
201     d->changes.insert(d->changeId, changeElement);
202 
203     return d->changeId++;
204 }
205 
elementById(int id) const206 KoChangeTrackerElement* KoChangeTracker::elementById(int id) const
207 {
208     if (isDuplicateChangeId(id)) {
209         id = originalChangeId(id);
210     }
211     return d->changes.value(id);
212 }
213 
removeById(int id,bool freeMemory)214 bool KoChangeTracker::removeById(int id, bool freeMemory)
215 {
216     if (freeMemory) {
217       KoChangeTrackerElement *temp = d->changes.value(id);
218       delete temp;
219     }
220     return d->changes.remove(id);
221 }
222 
containsInlineChanges(const QTextFormat & format) const223 bool KoChangeTracker::containsInlineChanges(const QTextFormat &format) const
224 {
225     if (format.property(KoCharacterStyle::ChangeTrackerId).toInt())
226         return true;
227 
228     return false;
229 }
230 
mergeableId(KoGenChange::Type type,const KUndo2MagicString & title,int existingId) const231 int KoChangeTracker::mergeableId(KoGenChange::Type type, const KUndo2MagicString &title, int existingId) const
232 {
233     if (!existingId || !d->changes.value(existingId))
234         return 0;
235 
236     if (d->changes.value(existingId)->getChangeType() == type && d->changes.value(existingId)->getChangeTitle() == title) {
237         return existingId;
238     }
239     else {
240         if (d->parents.contains(existingId)) {
241             return mergeableId(type, title, d->parents.value(existingId));
242         }
243         else {
244             return 0;
245         }
246     }
247 }
248 
split(int changeId)249 int KoChangeTracker::split(int changeId)
250 {
251     KoChangeTrackerElement *element = new KoChangeTrackerElement(*d->changes.value(changeId));
252     d->changes.insert(d->changeId, element);
253     return d->changeId++;
254 }
255 
isParent(int testedParentId,int testedChildId) const256 bool KoChangeTracker::isParent(int testedParentId, int testedChildId) const
257 {
258     if ((testedParentId == testedChildId) && !d->acceptedRejectedChanges.contains(testedParentId))
259         return true;
260     else if (d->parents.contains(testedChildId))
261         return isParent(testedParentId, d->parents.value(testedChildId));
262     else
263         return false;
264 }
265 
setParent(int child,int parent)266 void KoChangeTracker::setParent(int child, int parent)
267 {
268     if (!d->children.values(parent).contains(child)) {
269         d->children.insert(parent, child);
270     }
271     if (!d->parents.contains(child)) {
272         d->parents.insert(child, parent);
273     }
274 }
275 
parent(int changeId) const276 int KoChangeTracker::parent(int changeId) const
277 {
278     if (!d->parents.contains(changeId))
279         return 0;
280     if (d->acceptedRejectedChanges.contains(d->parents.value(changeId)))
281         return parent(d->parents.value(changeId));
282     return d->parents.value(changeId);
283 }
284 
createDuplicateChangeId(int existingChangeId)285 int KoChangeTracker::createDuplicateChangeId(int existingChangeId)
286 {
287     int duplicateChangeId = d->changeId;
288     d->changeId++;
289 
290     d->duplicateIds.insert(existingChangeId, duplicateChangeId);
291 
292     return duplicateChangeId;
293 }
294 
isDuplicateChangeId(int duplicateChangeId) const295 bool KoChangeTracker::isDuplicateChangeId(int duplicateChangeId) const
296 {
297     return d->duplicateIds.values().contains(duplicateChangeId);
298 }
299 
originalChangeId(int duplicateChangeId) const300 int KoChangeTracker::originalChangeId(int duplicateChangeId) const
301 {
302     int originalChangeId = 0;
303     QMultiHash<int, int>::const_iterator i = d->duplicateIds.constBegin();
304 
305     while (i != d->duplicateIds.constEnd()) {
306         if (duplicateChangeId == i.value()) {
307             originalChangeId = i.key();
308             break;
309         }
310         ++i;
311     }
312 
313     return originalChangeId;
314 }
315 
acceptRejectChange(int changeId,bool set)316 void KoChangeTracker::acceptRejectChange(int changeId, bool set)
317 {
318     if (set) {
319         if (!d->acceptedRejectedChanges.contains(changeId))
320             d->acceptedRejectedChanges.append(changeId);
321     }
322     else {
323         if (d->acceptedRejectedChanges.contains(changeId))
324             d->acceptedRejectedChanges.removeAll(changeId);
325     }
326 
327     d->changes.value(changeId)->setAcceptedRejected(set);
328 }
329 
saveInlineChange(int changeId,KoGenChange & change)330 bool KoChangeTracker::saveInlineChange(int changeId, KoGenChange &change)
331 {
332     if (!d->changes.contains(changeId))
333         return false;
334 
335     change.setType(d->changes.value(changeId)->getChangeType());
336     change.addChangeMetaData("dc-creator", d->changes.value(changeId)->getCreator());
337     change.addChangeMetaData("dc-date", d->changes.value(changeId)->getDate());
338     if (d->changes.value(changeId)->hasExtraMetaData())
339         change.addChildElement("changeMetaData", d->changes.value(changeId)->getExtraMetaData());
340 
341     return true;
342 }
343 
saveInlineChanges(QMap<int,QString> changeTransTable,KoGenChanges & genChanges)344 QMap<int, QString> KoChangeTracker::saveInlineChanges(QMap<int, QString> changeTransTable, KoGenChanges &genChanges)
345 {
346     foreach (int changeId, d->changes.keys()) {
347 
348         // return if the id we find in the changetranstable already has a length.
349         if (changeTransTable.value(changeId).length()) {
350             continue;
351         }
352 
353         if ((elementById(changeId)->getChangeType() == KoGenChange::DeleteChange) &&
354                 (saveFormat() == KoChangeTracker::ODF_1_2)) {
355             continue;
356         }
357 
358         KoGenChange change;
359         if (saveFormat() == KoChangeTracker::ODF_1_2) {
360             change.setChangeFormat(KoGenChange::ODF_1_2);
361         } else {
362             change.setChangeFormat(KoGenChange::DELTAXML);
363         }
364 
365         saveInlineChange(changeId, change);
366         QString changeName = genChanges.insert(change);
367         changeTransTable.insert(changeId, changeName);
368     }
369     return changeTransTable;
370 }
371 
setFormatChangeInformation(int formatChangeId,KoFormatChangeInformation * formatInformation)372 void KoChangeTracker::setFormatChangeInformation(int formatChangeId, KoFormatChangeInformation *formatInformation)
373 {
374     d->changeInformation.insert(formatChangeId, formatInformation);
375 }
376 
formatChangeInformation(int formatChangeId) const377 KoFormatChangeInformation *KoChangeTracker::formatChangeInformation(int formatChangeId) const
378 {
379     return d->changeInformation.value(formatChangeId);
380 }
381 
loadOdfChanges(const KoXmlElement & element)382 void KoChangeTracker::loadOdfChanges(const KoXmlElement& element)
383 {
384     if (element.namespaceURI() == KoXmlNS::text) {
385         KoXmlElement tag;
386         forEachElement(tag, element) {
387             if (! tag.isNull()) {
388                 const QString localName = tag.localName();
389                 if (localName == "changed-region") {
390                     KoChangeTrackerElement *changeElement = 0;
391                     KoXmlElement region;
392                     forEachElement(region, tag) {
393                         if (!region.isNull()) {
394                             if (region.localName() == "insertion") {
395                                 changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::InsertChange);
396                             } else if (region.localName() == "format-change") {
397                                 changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::FormatChange);
398                             } else if (region.localName() == "deletion") {
399                                 changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::DeleteChange);
400                             }
401                             KoXmlElement metadata = region.namedItemNS(KoXmlNS::office,"change-info").toElement();
402                             if (!metadata.isNull()) {
403                                 KoXmlElement date = metadata.namedItem("dc:date").toElement();
404                                 if (!date.isNull()) {
405                                     changeElement->setDate(date.text());
406                                 }
407                                 KoXmlElement creator = metadata.namedItem("dc:creator").toElement();
408                                 if (!date.isNull()) {
409                                     changeElement->setCreator(creator.text());
410                                 }
411                                 //TODO load comments
412 /*                              KoXmlElement extra = metadata.namedItem("dc-").toElement();
413                                 if (!date.isNull()) {
414                                     debugText << "creator: " << creator.text();
415                                     changeElement->setCreator(creator.text());
416                                 }*/
417                             }
418                             changeElement->setEnabled(d->recordChanges);
419                             d->changes.insert( d->changeId, changeElement);
420                             d->loadedChanges.insert(tag.attributeNS(KoXmlNS::text,"id"), d->changeId++);
421                         }
422                     }
423                 }
424             }
425         }
426     } else {
427         //This is the ODF 1.2 Change Format
428         KoXmlElement tag;
429         forEachElement(tag, element) {
430             if (! tag.isNull()) {
431                 const QString localName = tag.localName();
432                 if (localName == "change-transaction") {
433                     KoChangeTrackerElement *changeElement = 0;
434                     //Set the change element as an insertion element for now
435                     //Will be changed to the correct type when actual changes referencing this change-id are encountered
436                     changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::delta,"change-id")),KoGenChange::InsertChange);
437                     KoXmlElement metadata = tag.namedItemNS(KoXmlNS::delta,"change-info").toElement();
438                     if (!metadata.isNull()) {
439                            KoXmlElement date = metadata.namedItem("dc:date").toElement();
440                            if (!date.isNull()) {
441                                 changeElement->setDate(date.text());
442                             }
443                             KoXmlElement creator = metadata.namedItem("dc:creator").toElement();
444                             if (!creator.isNull()) {
445                                 changeElement->setCreator(creator.text());
446                             }
447                     }
448                     changeElement->setEnabled(d->recordChanges);
449                     d->changes.insert( d->changeId, changeElement);
450                     d->loadedChanges.insert(tag.attributeNS(KoXmlNS::delta,"change-id"), d->changeId++);
451                }
452            }
453         }
454     }
455 }
456 
getLoadedChangeId(const QString & odfId) const457 int KoChangeTracker::getLoadedChangeId(const QString &odfId) const
458 {
459     return d->loadedChanges.value(odfId);
460 }
461 
getDeletedChanges(QVector<KoChangeTrackerElement * > & deleteVector) const462 int KoChangeTracker::getDeletedChanges(QVector<KoChangeTrackerElement *>& deleteVector) const
463 {
464     int numAppendedItems = 0;
465     foreach (KoChangeTrackerElement *element, d->changes) {
466         if(element->getChangeType() == KoGenChange::DeleteChange && !element->acceptedRejected()) {
467           deleteVector << element;
468           numAppendedItems++;
469         }
470     }
471 
472     return numAppendedItems;
473 }
474 
getInsertionBgColor() const475 QColor KoChangeTracker::getInsertionBgColor() const
476 {
477     return d->insertionBgColor;
478 }
479 
getDeletionBgColor() const480 QColor KoChangeTracker::getDeletionBgColor() const
481 {
482     return d->deletionBgColor;
483 }
484 
getFormatChangeBgColor() const485 QColor KoChangeTracker::getFormatChangeBgColor() const
486 {
487     return d->formatChangeBgColor;
488 }
489 
setInsertionBgColor(const QColor & bgColor)490 void KoChangeTracker::setInsertionBgColor(const QColor& bgColor)
491 {
492     d->insertionBgColor = bgColor;
493 }
494 
setDeletionBgColor(const QColor & bgColor)495 void KoChangeTracker::setDeletionBgColor(const QColor& bgColor)
496 {
497     d->deletionBgColor = bgColor;
498 }
499 
setFormatChangeBgColor(const QColor & bgColor)500 void KoChangeTracker::setFormatChangeBgColor(const QColor& bgColor)
501 {
502     d->formatChangeBgColor = bgColor;
503 }
504 
505 ////A convenience function to get a ListIdType from a format
506 //static KoListStyle::ListIdType ListId(const QTextListFormat &format)
507 //{
508 //    KoListStyle::ListIdType listId;
509 
510 //    if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) {
511 //        listId = format.property(KoListStyle::ListId).toUInt();
512 //    }
513 //    else {
514 //        listId = format.property(KoListStyle::ListId).toULongLong();
515 //    }
516 
517 //    return listId;
518 //}
519 
generateDeleteFragment(const QTextCursor & cursor)520 QTextDocumentFragment KoChangeTracker::generateDeleteFragment(const QTextCursor &cursor)
521 {
522     QTextCursor editCursor(cursor);
523     QTextDocument *document = cursor.document();
524 
525     QTextDocument deletedDocument;
526     QTextDocument deleteCursor(&deletedDocument);
527 
528     KoInlineTextObjectManager *textObjectManager = KoTextDocument(document).inlineTextObjectManager();
529     if (textObjectManager) {
530         for (int i = cursor.anchor();i <= cursor.position(); i++) {
531             if (document->characterAt(i) == QChar::ObjectReplacementCharacter) {
532                 editCursor.setPosition(i+1);
533 	    }
534         }
535     }
536 
537     QTextBlock currentBlock = document->findBlock(cursor.anchor());
538     QTextBlock startBlock = currentBlock;
539     QTextBlock endBlock = document->findBlock(cursor.position()).next();
540 
541     currentBlock = document->findBlock(cursor.anchor());
542     startBlock = currentBlock;
543     endBlock = document->findBlock(cursor.position()).next();
544 
545     for (;currentBlock != endBlock; currentBlock = currentBlock.next()) {
546         editCursor.setPosition(currentBlock.position());
547         if (editCursor.currentTable()) {
548             QTextTableFormat tableFormat = editCursor.currentTable()->format();
549             editCursor.currentTable()->setFormat(tableFormat);
550         }
551 
552         if (currentBlock != startBlock) {
553             QTextBlockFormat blockFormat;
554             editCursor.mergeBlockFormat(blockFormat);
555         }
556     }
557 
558     return cursor.selection();
559 }
560 
checkListDeletion(const QTextList & list,const QTextCursor & cursor)561 bool KoChangeTracker::checkListDeletion(const QTextList &list, const QTextCursor &cursor)
562 {
563     int startOfList = (list.item(0).position() - 1);
564     int endOfList = list.item(list.count() -1).position() + list.item(list.count() -1).length() - 1;
565     if ((cursor.anchor() <= startOfList) && (cursor.position() >= endOfList))
566         return true;
567     else {
568         /***************************************************************************************************/
569         /*                                    Qt Quirk Work-Around                                         */
570         /***************************************************************************************************/
571         if ((cursor.anchor() == (startOfList + 1)) && (cursor.position() > endOfList)) {
572             return true;
573         /***************************************************************************************************/
574         } else if((cursor.anchor() <= startOfList) && (list.count() == 1)) {
575             return true;
576         } else {
577             return false;
578         }
579     }
580 }
581 
insertDeleteFragment(QTextCursor & cursor)582 void KoChangeTracker::insertDeleteFragment(QTextCursor &cursor)
583 {
584     QTextDocument tempDoc;
585     QTextCursor tempCursor(&tempDoc);
586 
587     bool deletedListItem = false;
588 
589     for (QTextBlock currentBlock = tempDoc.begin(); currentBlock != tempDoc.end(); currentBlock = currentBlock.next()) {
590         //This condition is for the work-around for a Qt behaviour
591         //Even if a delete ends at the end of a table, the fragment will have an empty block after the table
592         //If such a block is detected then, just ignore it
593         if ((currentBlock.next() == tempDoc.end()) && (currentBlock.text().length() == 0) && (QTextCursor(currentBlock.previous()).currentTable())) {
594             continue;
595         }
596 
597         tempCursor.setPosition(currentBlock.position());
598         QTextList *textList = tempCursor.currentList();
599         int outlineLevel = currentBlock.blockFormat().property(KoParagraphStyle::OutlineLevel).toInt();
600 
601         KoList *currentList = KoTextDocument(cursor.document()).list(cursor.block());
602         int docOutlineLevel = cursor.block().blockFormat().property(KoParagraphStyle::OutlineLevel).toInt();
603         if (docOutlineLevel) {
604             //Even though we got a list, it is actually a list for storing headings. So don't consider it
605             currentList = nullptr;
606         }
607 
608         QTextList *previousTextList = currentBlock.previous().isValid() ? QTextCursor(currentBlock.previous()).currentList():nullptr;
609         if (textList && previousTextList && (textList != previousTextList) && (KoList::level(currentBlock) == KoList::level(currentBlock.previous()))) {
610             //Even though we are already in a list, the QTextList* of the current block is different from that of the previous block
611             //Also the levels of the list-items ( previous and current ) are the same.
612             //This can happen only when two lists are merged together without any intermediate content.
613             //So we need to create a new list.
614             currentList = nullptr;
615         }
616 
617         if (textList) {
618             if (deletedListItem && currentBlock != tempDoc.begin()) {
619                 // Found a deleted list item in the fragment. So insert a new list-item
620                 int deletedListItemLevel = KoList::level(currentBlock);
621 
622                 if (!(QTextCursor(currentBlock.previous()).currentTable())) {
623                     cursor.insertBlock(currentBlock.blockFormat(), currentBlock.charFormat());
624                 } else {
625                     cursor.mergeBlockFormat(currentBlock.blockFormat());
626                 }
627 
628                 if(!currentList) {
629                     if (!outlineLevel) {
630                         //This happens when a part of a paragraph and a succeeding list-item are deleted together
631                         //So go to the next block and insert it in the list there.
632                         QTextCursor tmp(cursor);
633                         tmp.setPosition(tmp.block().next().position());
634                         currentList = KoTextDocument(tmp.document()).list(tmp.block());
635                     } else {
636                         // This is a heading. So find the KoList for heading and add the block there
637                         KoList *headingList = KoTextDocument(cursor.document()).headingList();
638                         currentList = headingList;
639                     }
640                 }
641                 currentList->add(cursor.block(), deletedListItemLevel);
642             }
643         } else if (tempCursor.currentTable()) {
644             QTextTable *deletedTable = tempCursor.currentTable();
645             int numRows = deletedTable->rows();
646             int numColumns = deletedTable->columns();
647             QTextTable *insertedTable = cursor.insertTable(numRows, numColumns, deletedTable->format());
648             for (int i=0; i<numRows; i++) {
649                 for (int j=0; j<numColumns; j++) {
650                     tempCursor.setPosition(deletedTable->cellAt(i,j).firstCursorPosition().position());
651                     tempCursor.setPosition(deletedTable->cellAt(i,j).lastCursorPosition().position(), QTextCursor::KeepAnchor);
652                     insertedTable->cellAt(i,j).setFormat(deletedTable->cellAt(i,j).format().toTableCellFormat());
653                     cursor.setPosition(insertedTable->cellAt(i,j).firstCursorPosition().position());
654                     cursor.insertFragment(tempCursor.selection());
655                 }
656             }
657             tempCursor.setPosition(deletedTable->cellAt(numRows-1,numColumns-1).lastCursorPosition().position());
658             currentBlock = tempCursor.block();
659             //Move the cursor outside of table
660             cursor.setPosition(cursor.position() + 1);
661             continue;
662         } else {
663             // This block does not contain a list. So no special work here.
664             if ((currentBlock != tempDoc.begin()) && !(QTextCursor(currentBlock.previous()).currentTable())) {
665                 cursor.insertBlock(currentBlock.blockFormat(), currentBlock.charFormat());
666             }
667 
668             if (QTextCursor(currentBlock.previous()).currentTable()) {
669                 cursor.mergeBlockFormat(currentBlock.blockFormat());
670             }
671         }
672 
673         /********************************************************************************************************************/
674         /*This section of code is a work-around for a bug in the Qt. This work-around is safe. If and when the bug is fixed */
675         /*the if condition would never be true and the code would never get executed                                        */
676         /********************************************************************************************************************/
677         if ((KoList::level(cursor.block()) != KoList::level(currentBlock)) && currentBlock.text().length()) {
678             if (!currentList) {
679                 QTextCursor tmp(cursor);
680                 tmp.setPosition(tmp.block().previous().position());
681                 currentList = KoTextDocument(tmp.document()).list(tmp.block());
682             }
683             currentList->add(cursor.block(), KoList::level(currentBlock));
684         }
685         /********************************************************************************************************************/
686 
687         // Finally insert all the contents of the block into the main document.
688         QTextBlock::iterator it;
689         for (it = currentBlock.begin(); !(it.atEnd()); ++it) {
690             QTextFragment currentFragment = it.fragment();
691             if (currentFragment.isValid()) {
692                 cursor.insertText(currentFragment.text(), currentFragment.charFormat());
693             }
694         }
695     }
696 }
697 
fragmentLength(const QTextDocumentFragment & fragment)698 int KoChangeTracker::fragmentLength(const QTextDocumentFragment &fragment)
699 {
700     QTextDocument tempDoc;
701     QTextCursor tempCursor(&tempDoc);
702     tempCursor.insertFragment(fragment);
703     int length = 0;
704     bool deletedListItem = false;
705     for (QTextBlock currentBlock = tempDoc.begin(); currentBlock != tempDoc.end(); currentBlock = currentBlock.next()) {
706         tempCursor.setPosition(currentBlock.position());
707         if (tempCursor.currentList()) {
708             if (currentBlock != tempDoc.begin() && deletedListItem)
709                 length += 1; //For the Block separator
710         } else if (tempCursor.currentTable()) {
711             QTextTable *deletedTable = tempCursor.currentTable();
712             int numRows = deletedTable->rows();
713             int numColumns = deletedTable->columns();
714             for (int i=0; i<numRows; i++) {
715                 for (int j=0; j<numColumns; j++) {
716                     length += 1;
717                     length += (deletedTable->cellAt(i,j).lastCursorPosition().position() - deletedTable->cellAt(i,j).firstCursorPosition().position());
718                 }
719             }
720             tempCursor.setPosition(deletedTable->cellAt(numRows-1,numColumns-1).lastCursorPosition().position());
721             currentBlock = tempCursor.block();
722             length += 1;
723             continue;
724         } else {
725             if ((currentBlock != tempDoc.begin()) && !(QTextCursor(currentBlock.previous()).currentTable()))
726                 length += 1; //For the Block Separator
727         }
728 
729 
730         QTextBlock::iterator it;
731         for (it = currentBlock.begin(); !(it.atEnd()); ++it) {
732             QTextFragment currentFragment = it.fragment();
733             if (currentFragment.isValid())
734                 length += currentFragment.text().length();
735         }
736     }
737 
738     return length;
739 }
740