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@valdyas.org>
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
37 //KDE includes
38 #include "TextDebug.h"
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 &)382 void KoChangeTracker::loadOdfChanges(const KoXmlElement& )
383 {
384 }
385
getLoadedChangeId(const QString & odfId) const386 int KoChangeTracker::getLoadedChangeId(const QString &odfId) const
387 {
388 return d->loadedChanges.value(odfId);
389 }
390
getDeletedChanges(QVector<KoChangeTrackerElement * > & deleteVector) const391 int KoChangeTracker::getDeletedChanges(QVector<KoChangeTrackerElement *>& deleteVector) const
392 {
393 int numAppendedItems = 0;
394 foreach (KoChangeTrackerElement *element, d->changes.values()) {
395 if(element->getChangeType() == KoGenChange::DeleteChange && !element->acceptedRejected()) {
396 deleteVector << element;
397 numAppendedItems++;
398 }
399 }
400
401 return numAppendedItems;
402 }
403
getInsertionBgColor() const404 QColor KoChangeTracker::getInsertionBgColor() const
405 {
406 return d->insertionBgColor;
407 }
408
getDeletionBgColor() const409 QColor KoChangeTracker::getDeletionBgColor() const
410 {
411 return d->deletionBgColor;
412 }
413
getFormatChangeBgColor() const414 QColor KoChangeTracker::getFormatChangeBgColor() const
415 {
416 return d->formatChangeBgColor;
417 }
418
setInsertionBgColor(const QColor & bgColor)419 void KoChangeTracker::setInsertionBgColor(const QColor& bgColor)
420 {
421 d->insertionBgColor = bgColor;
422 }
423
setDeletionBgColor(const QColor & bgColor)424 void KoChangeTracker::setDeletionBgColor(const QColor& bgColor)
425 {
426 d->deletionBgColor = bgColor;
427 }
428
setFormatChangeBgColor(const QColor & bgColor)429 void KoChangeTracker::setFormatChangeBgColor(const QColor& bgColor)
430 {
431 d->formatChangeBgColor = bgColor;
432 }
433
434 ////A convenience function to get a ListIdType from a format
435 //static KoListStyle::ListIdType ListId(const QTextListFormat &format)
436 //{
437 // KoListStyle::ListIdType listId;
438
439 // if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) {
440 // listId = format.property(KoListStyle::ListId).toUInt();
441 // }
442 // else {
443 // listId = format.property(KoListStyle::ListId).toULongLong();
444 // }
445
446 // return listId;
447 //}
448
generateDeleteFragment(const QTextCursor & cursor)449 QTextDocumentFragment KoChangeTracker::generateDeleteFragment(const QTextCursor &cursor)
450 {
451 QTextCursor editCursor(cursor);
452 QTextDocument *document = cursor.document();
453
454 QTextDocument deletedDocument;
455 QTextDocument deleteCursor(&deletedDocument);
456
457 KoInlineTextObjectManager *textObjectManager = KoTextDocument(document).inlineTextObjectManager();
458 if (textObjectManager) {
459 for (int i = cursor.anchor();i <= cursor.position(); i++) {
460 if (document->characterAt(i) == QChar::ObjectReplacementCharacter) {
461 editCursor.setPosition(i+1);
462 }
463 }
464 }
465
466 QTextBlock currentBlock = document->findBlock(cursor.anchor());
467 QTextBlock startBlock = currentBlock;
468 QTextBlock endBlock = document->findBlock(cursor.position()).next();
469
470 currentBlock = document->findBlock(cursor.anchor());
471 startBlock = currentBlock;
472 endBlock = document->findBlock(cursor.position()).next();
473
474 for (;currentBlock != endBlock; currentBlock = currentBlock.next()) {
475 editCursor.setPosition(currentBlock.position());
476 if (editCursor.currentTable()) {
477 QTextTableFormat tableFormat = editCursor.currentTable()->format();
478 editCursor.currentTable()->setFormat(tableFormat);
479 }
480
481 if (currentBlock != startBlock) {
482 QTextBlockFormat blockFormat;
483 editCursor.mergeBlockFormat(blockFormat);
484 }
485 }
486
487 return cursor.selection();
488 }
489
checkListDeletion(const QTextList & list,const QTextCursor & cursor)490 bool KoChangeTracker::checkListDeletion(const QTextList &list, const QTextCursor &cursor)
491 {
492 int startOfList = (list.item(0).position() - 1);
493 int endOfList = list.item(list.count() -1).position() + list.item(list.count() -1).length() - 1;
494 if ((cursor.anchor() <= startOfList) && (cursor.position() >= endOfList))
495 return true;
496 else {
497 /***************************************************************************************************/
498 /* Qt Quirk Work-Around */
499 /***************************************************************************************************/
500 if ((cursor.anchor() == (startOfList + 1)) && (cursor.position() > endOfList)) {
501 return true;
502 /***************************************************************************************************/
503 } else if((cursor.anchor() <= startOfList) && (list.count() == 1)) {
504 return true;
505 } else {
506 return false;
507 }
508 }
509 }
510
insertDeleteFragment(QTextCursor & cursor)511 void KoChangeTracker::insertDeleteFragment(QTextCursor &cursor)
512 {
513 QTextDocument tempDoc;
514 QTextCursor tempCursor(&tempDoc);
515
516 bool deletedListItem = false;
517
518 for (QTextBlock currentBlock = tempDoc.begin(); currentBlock != tempDoc.end(); currentBlock = currentBlock.next()) {
519 //This condition is for the work-around for a Qt behaviour
520 //Even if a delete ends at the end of a table, the fragment will have an empty block after the table
521 //If such a block is detected then, just ignore it
522 if ((currentBlock.next() == tempDoc.end()) && (currentBlock.text().length() == 0) && (QTextCursor(currentBlock.previous()).currentTable())) {
523 continue;
524 }
525
526 tempCursor.setPosition(currentBlock.position());
527 QTextList *textList = tempCursor.currentList();
528 int outlineLevel = currentBlock.blockFormat().property(KoParagraphStyle::OutlineLevel).toInt();
529
530 KoList *currentList = KoTextDocument(cursor.document()).list(cursor.block());
531 int docOutlineLevel = cursor.block().blockFormat().property(KoParagraphStyle::OutlineLevel).toInt();
532 if (docOutlineLevel) {
533 //Even though we got a list, it is actually a list for storing headings. So don't consider it
534 currentList = 0;
535 }
536
537 QTextList *previousTextList = currentBlock.previous().isValid() ? QTextCursor(currentBlock.previous()).currentList():0;
538 if (textList && previousTextList && (textList != previousTextList) && (KoList::level(currentBlock) == KoList::level(currentBlock.previous()))) {
539 //Even though we are already in a list, the QTextList* of the current block is different from that of the previous block
540 //Also the levels of the list-items ( previous and current ) are the same.
541 //This can happen only when two lists are merged together without any intermediate content.
542 //So we need to create a new list.
543 currentList = 0;
544 }
545
546 if (textList) {
547 if (deletedListItem && currentBlock != tempDoc.begin()) {
548 // Found a deleted list item in the fragment. So insert a new list-item
549 int deletedListItemLevel = KoList::level(currentBlock);
550
551 if (!(QTextCursor(currentBlock.previous()).currentTable())) {
552 cursor.insertBlock(currentBlock.blockFormat(), currentBlock.charFormat());
553 } else {
554 cursor.mergeBlockFormat(currentBlock.blockFormat());
555 }
556
557 if(!currentList) {
558 if (!outlineLevel) {
559 //This happens when a part of a paragraph and a succeeding list-item are deleted together
560 //So go to the next block and insert it in the list there.
561 QTextCursor tmp(cursor);
562 tmp.setPosition(tmp.block().next().position());
563 currentList = KoTextDocument(tmp.document()).list(tmp.block());
564 } else {
565 // This is a heading. So find the KoList for heading and add the block there
566 KoList *headingList = KoTextDocument(cursor.document()).headingList();
567 currentList = headingList;
568 }
569 }
570 currentList->add(cursor.block(), deletedListItemLevel);
571 }
572 } else if (tempCursor.currentTable()) {
573 QTextTable *deletedTable = tempCursor.currentTable();
574 int numRows = deletedTable->rows();
575 int numColumns = deletedTable->columns();
576 QTextTable *insertedTable = cursor.insertTable(numRows, numColumns, deletedTable->format());
577 for (int i=0; i<numRows; i++) {
578 for (int j=0; j<numColumns; j++) {
579 tempCursor.setPosition(deletedTable->cellAt(i,j).firstCursorPosition().position());
580 tempCursor.setPosition(deletedTable->cellAt(i,j).lastCursorPosition().position(), QTextCursor::KeepAnchor);
581 insertedTable->cellAt(i,j).setFormat(deletedTable->cellAt(i,j).format().toTableCellFormat());
582 cursor.setPosition(insertedTable->cellAt(i,j).firstCursorPosition().position());
583 cursor.insertFragment(tempCursor.selection());
584 }
585 }
586 tempCursor.setPosition(deletedTable->cellAt(numRows-1,numColumns-1).lastCursorPosition().position());
587 currentBlock = tempCursor.block();
588 //Move the cursor outside of table
589 cursor.setPosition(cursor.position() + 1);
590 continue;
591 } else {
592 // This block does not contain a list. So no special work here.
593 if ((currentBlock != tempDoc.begin()) && !(QTextCursor(currentBlock.previous()).currentTable())) {
594 cursor.insertBlock(currentBlock.blockFormat(), currentBlock.charFormat());
595 }
596
597 if (QTextCursor(currentBlock.previous()).currentTable()) {
598 cursor.mergeBlockFormat(currentBlock.blockFormat());
599 }
600 }
601
602 /********************************************************************************************************************/
603 /*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 */
604 /*the if condition would never be true and the code would never get executed */
605 /********************************************************************************************************************/
606 if ((KoList::level(cursor.block()) != KoList::level(currentBlock)) && currentBlock.text().length()) {
607 if (!currentList) {
608 QTextCursor tmp(cursor);
609 tmp.setPosition(tmp.block().previous().position());
610 currentList = KoTextDocument(tmp.document()).list(tmp.block());
611 }
612 currentList->add(cursor.block(), KoList::level(currentBlock));
613 }
614 /********************************************************************************************************************/
615
616 // Finally insert all the contents of the block into the main document.
617 QTextBlock::iterator it;
618 for (it = currentBlock.begin(); !(it.atEnd()); ++it) {
619 QTextFragment currentFragment = it.fragment();
620 if (currentFragment.isValid()) {
621 cursor.insertText(currentFragment.text(), currentFragment.charFormat());
622 }
623 }
624 }
625 }
626
fragmentLength(const QTextDocumentFragment & fragment)627 int KoChangeTracker::fragmentLength(const QTextDocumentFragment &fragment)
628 {
629 QTextDocument tempDoc;
630 QTextCursor tempCursor(&tempDoc);
631 tempCursor.insertFragment(fragment);
632 int length = 0;
633 bool deletedListItem = false;
634 for (QTextBlock currentBlock = tempDoc.begin(); currentBlock != tempDoc.end(); currentBlock = currentBlock.next()) {
635 tempCursor.setPosition(currentBlock.position());
636 if (tempCursor.currentList()) {
637 if (currentBlock != tempDoc.begin() && deletedListItem)
638 length += 1; //For the Block separator
639 } else if (tempCursor.currentTable()) {
640 QTextTable *deletedTable = tempCursor.currentTable();
641 int numRows = deletedTable->rows();
642 int numColumns = deletedTable->columns();
643 for (int i=0; i<numRows; i++) {
644 for (int j=0; j<numColumns; j++) {
645 length += 1;
646 length += (deletedTable->cellAt(i,j).lastCursorPosition().position() - deletedTable->cellAt(i,j).firstCursorPosition().position());
647 }
648 }
649 tempCursor.setPosition(deletedTable->cellAt(numRows-1,numColumns-1).lastCursorPosition().position());
650 currentBlock = tempCursor.block();
651 length += 1;
652 continue;
653 } else {
654 if ((currentBlock != tempDoc.begin()) && !(QTextCursor(currentBlock.previous()).currentTable()))
655 length += 1; //For the Block Separator
656 }
657
658
659 QTextBlock::iterator it;
660 for (it = currentBlock.begin(); !(it.atEnd()); ++it) {
661 QTextFragment currentFragment = it.fragment();
662 if (currentFragment.isValid())
663 length += currentFragment.text().length();
664 }
665 }
666
667 return length;
668 }
669