1 /*
2 This file is part of the KDE project
3 * Copyright (C) 2009 Ganesh Paramasivam <ganesh@crystalfab.com>
4 * Copyright (C) 2009 Pierre Stirnweiss <pstirnweiss@googlemail.com>
5 * Copyright (C) 2010 Thomas Zander <zander@kde.org>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.*/
21
22 #include "ChangeTrackedDeleteCommand.h"
23
24 #include "TextPasteCommand.h"
25 #include "ListItemNumberingCommand.h"
26 #include "ChangeListCommand.h"
27
28 #include <KoTextEditor.h>
29 #include <KoChangeTracker.h>
30 #include <KoChangeTrackerElement.h>
31 #include <KoTextDocument.h>
32 #include <KoInlineTextObjectManager.h>
33 #include <KoShapeController.h>
34 #include <KoList.h>
35 #include <KoParagraphStyle.h>
36 #include <KoTextOdfSaveHelper.h>
37 #include <KoTextDrag.h>
38 #include <KoOdf.h>
39 #include <KoDocumentRdfBase.h>
40
41 #include <QTextDocumentFragment>
42
43 #include <klocalizedstring.h>
44
45 #include "TextDebug.h"
46
47 //A convenience function to get a ListIdType from a format
48
ListId(const QTextListFormat & format)49 static KoListStyle::ListIdType ListId(const QTextListFormat &format)
50 {
51 KoListStyle::ListIdType listId;
52
53 if (sizeof(KoListStyle::ListIdType) == sizeof(uint))
54 listId = format.property(KoListStyle::ListId).toUInt();
55 else
56 listId = format.property(KoListStyle::ListId).toULongLong();
57
58 return listId;
59 }
60
61 using namespace std;
ChangeTrackedDeleteCommand(DeleteMode mode,QTextDocument * document,KoShapeController * shapeController,KUndo2Command * parent)62 ChangeTrackedDeleteCommand::ChangeTrackedDeleteCommand(DeleteMode mode,
63 QTextDocument *document,
64 KoShapeController *shapeController,
65 KUndo2Command *parent) :
66 KoTextCommandBase (parent),
67 m_document(document),
68 m_rdf(0),
69 m_shapeController(shapeController),
70 m_first(true),
71 m_undone(false),
72 m_canMerge(true),
73 m_mode(mode),
74 m_removedElements()
75 {
76 setText(kundo2_i18n("Delete"));
77 m_rdf = qobject_cast<KoDocumentRdfBase*>(shapeController->resourceManager()->resource(KoText::DocumentRdf).value<QObject*>());
78 }
79
undo()80 void ChangeTrackedDeleteCommand::undo()
81 {
82 if (m_document.isNull()) return;
83
84 KoTextCommandBase::undo();
85 UndoRedoFinalizer finalizer(this);
86
87 KoTextDocument textDocument(m_document.data());
88 textDocument.changeTracker()->elementById(m_addedChangeElement)->setValid(false);
89 foreach (int changeId, m_removedElements) {
90 textDocument.changeTracker()->elementById(changeId)->setValid(true);
91 }
92 updateListChanges();
93 m_undone = true;
94 }
95
redo()96 void ChangeTrackedDeleteCommand::redo()
97 {
98 if (!m_document.isNull()) return;
99
100 m_undone = false;
101 KoTextDocument textDocument(m_document.data());
102
103 if (!m_first) {
104 KoTextCommandBase::redo();
105 UndoRedoFinalizer finalizer(this);
106 textDocument.changeTracker()->elementById(m_addedChangeElement)->setValid(true);
107 foreach (int changeId, m_removedElements) {
108 textDocument.changeTracker()->elementById(changeId)->setValid(false);
109 }
110 } else {
111 m_first = false;
112 textDocument.textEditor()->beginEditBlock();
113 if(m_mode == PreviousChar)
114 deletePreviousChar();
115 else
116 deleteChar();
117 textDocument.textEditor()->endEditBlock();
118 }
119 }
120
deleteChar()121 void ChangeTrackedDeleteCommand::deleteChar()
122 {
123 if (m_document.isNull()) return;
124
125 KoTextEditor *editor = KoTextDocument(m_document).textEditor();
126
127 if (editor->atEnd() && !editor->hasSelection())
128 return;
129
130 if (!editor->hasSelection())
131 editor->movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
132
133 deleteSelection(editor);
134 }
135
deletePreviousChar()136 void ChangeTrackedDeleteCommand::deletePreviousChar()
137 {
138 if (m_document.isNull()) return;
139
140 KoTextEditor *editor = KoTextDocument(m_document).textEditor();
141
142 if (editor->atStart() && !editor->hasSelection())
143 return;
144
145 if (!editor->hasSelection()
146 && editor->block().textList()
147 && (editor->position() == editor->block().position())) {
148 handleListItemDelete(editor);
149 return;
150 }
151
152 if (!editor->hasSelection()) {
153 editor->movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
154 }
155 deleteSelection(editor);
156 }
157
handleListItemDelete(KoTextEditor * editor)158 void ChangeTrackedDeleteCommand::handleListItemDelete(KoTextEditor *editor)
159 {
160 if (m_document.isNull()) return;
161
162 m_canMerge = false;
163 bool numberedListItem = false;
164 if (!editor->blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem))
165 numberedListItem = true;
166
167 // Mark the complete list-item
168 QTextBlock block = m_document.data()->findBlock(editor->position());
169 editor->movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, (block.length() - 1));
170
171 // Copy the marked item
172 int from = editor->anchor();
173 int to = editor->position();
174 KoTextOdfSaveHelper saveHelper(m_document.data(), from, to);
175 KoTextDrag drag;
176 #ifdef SHOULD_BUILD_RDF
177 if (m_rdf) {
178 saveHelper.setRdfModel(m_rdf->model());
179 }
180 #endif
181 drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper);
182 QTextDocumentFragment fragment = editor->selection();
183 drag.setData("text/html", fragment.toHtml("utf-8").toUtf8());
184 drag.setData("text/plain", fragment.toPlainText().toUtf8());
185
186 // Delete the marked section
187 editor->setPosition(editor->anchor() -1);
188 editor->movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, block.length());
189 deleteSelection(editor);
190 // Mark it as inserted content
191 QTextCharFormat format = editor->charFormat();
192 editor->registerTrackedChange(*editor->cursor(), KoGenChange::InsertChange, i18n("Key Press"), format, format, false);
193 //Paste the selected text from the clipboard... (XXX: is this really correct here?)
194 TextPasteCommand *pasteCommand =
195 new TextPasteCommand(drag.mimeData(),
196 m_document.data(),
197 m_shapeController,
198 this);
199
200 pasteCommand->redo();
201
202 // Convert it into a un-numbered list-item or a paragraph
203 if (numberedListItem) {
204 ListItemNumberingCommand *changeNumberingCommand = new ListItemNumberingCommand(editor->block(), false, this);
205 changeNumberingCommand->redo();
206 } else {
207 KoListLevelProperties llp;
208 llp.setStyle(KoListStyle::None);
209 llp.setLevel(0);
210 ChangeListCommand *changeListCommand = new ChangeListCommand(*editor->cursor(), llp,
211 KoTextEditor::ModifyExistingList | KoTextEditor::MergeWithAdjacentList,
212 this);
213 changeListCommand->redo();
214 }
215 editor->setPosition(editor->block().position());
216 }
217
deleteSelection(KoTextEditor * editor)218 void ChangeTrackedDeleteCommand::deleteSelection(KoTextEditor *editor)
219 {
220 if (m_document.isNull()) return;
221
222 // XXX: don't allow anyone to steal our cursor!
223 QTextCursor *selection = editor->cursor();
224 QTextCursor checker = QTextCursor(*editor->cursor());
225
226 bool backwards = (checker.anchor() > checker.position());
227 int selectionBegin = qMin(checker.anchor(), checker.position());
228 int selectionEnd = qMax(checker.anchor(), checker.position());
229 int changeId;
230
231 QList<KoShape *> shapesInSelection;
232
233 checker.setPosition(selectionBegin);
234
235 KoTextDocument textDocument(m_document.data());
236 KoInlineTextObjectManager *inlineTextObjectManager = textDocument.inlineTextObjectManager();
237
238 while ((checker.position() < selectionEnd) && (!checker.atEnd())) {
239 QChar charAtPos = m_document.data()->characterAt(checker.position());
240 checker.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
241 if (inlineTextObjectManager->inlineTextObject(checker) && charAtPos == QChar::ObjectReplacementCharacter) {
242 /* This has changed but since this entire command is going away - let's not bother
243 KoTextAnchor *anchor = dynamic_cast<KoTextAnchor *>(inlineTextObjectManager->inlineTextObject(checker));
244 if (anchor)
245 shapesInSelection.push_back(anchor->shape());
246 */
247 }
248 checker.setPosition(checker.position());
249 }
250
251 checker.setPosition(selectionBegin);
252
253 if (!KoTextDocument(m_document).changeTracker()->displayChanges()) {
254 QChar charAtPos = m_document.data()->characterAt(checker.position() - 1);
255 }
256
257 if (KoTextDocument(m_document).changeTracker()->containsInlineChanges(checker.charFormat())) {
258 int changeId = checker.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt();
259 if (KoTextDocument(m_document).changeTracker()->elementById(changeId)->getChangeType() == KoGenChange::DeleteChange) {
260 QTextDocumentFragment prefix = KoTextDocument(m_document).changeTracker()->elementById(changeId)->getDeleteData();
261 selectionBegin -= (KoChangeTracker::fragmentLength(prefix) + 1 );
262 KoTextDocument(m_document).changeTracker()->elementById(changeId)->setValid(false);
263 m_removedElements.push_back(changeId);
264 }
265 }
266
267 checker.setPosition(selectionEnd);
268 if (!checker.atEnd()) {
269 QChar charAtPos = m_document.data()->characterAt(checker.position());
270 checker.movePosition(QTextCursor::NextCharacter);
271 }
272
273 selection->setPosition(selectionBegin);
274 selection->setPosition(selectionEnd, QTextCursor::KeepAnchor);
275 QTextDocumentFragment deletedFragment;
276 changeId = KoTextDocument(m_document).changeTracker()->getDeleteChangeId(i18n("Delete"), deletedFragment, 0);
277 KoChangeTrackerElement *element = KoTextDocument(m_document).changeTracker()->elementById(changeId);
278
279 QTextCharFormat charFormat;
280 charFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId);
281 selection->mergeCharFormat(charFormat);
282
283 deletedFragment = KoChangeTracker::generateDeleteFragment(*selection);
284 element->setDeleteData(deletedFragment);
285
286 //Store the position and length. Will be used in updateListChanges()
287 m_position = (selection->anchor() < selection->position()) ? selection->anchor():selection->position();
288 m_length = qAbs(selection->anchor() - selection->position());
289
290 updateListIds(*editor->cursor());
291
292 m_addedChangeElement = changeId;
293
294 //Insert the deleted data again after the marker with the charformat set to the change-id
295 if (KoTextDocument(m_document).changeTracker()->displayChanges()) {
296 int startPosition = selection->position();
297 KoChangeTracker::insertDeleteFragment(*selection);
298 QTextCursor tempCursor(*selection);
299 tempCursor.setPosition(startPosition);
300 tempCursor.setPosition(selection->position(), QTextCursor::KeepAnchor);
301 // XXX: why was this commented out?
302 //tempCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, fragmentLength(deletedFragment));
303 updateListIds(tempCursor);
304 if (backwards) {
305 selection->movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, KoChangeTracker::fragmentLength(deletedFragment) + 1);
306 }
307 } else {
308 if (backwards) {
309 selection->movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor,1);
310 }
311
312 foreach (KoShape *shape, shapesInSelection) {
313 KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this);
314 shapeDeleteCommand->redo();
315 m_canMerge = false;
316 }
317 }
318 }
319
id() const320 int ChangeTrackedDeleteCommand::id() const
321 {
322 return 98765;
323 }
324
mergeWith(const KUndo2Command * command)325 bool ChangeTrackedDeleteCommand::mergeWith( const KUndo2Command *command)
326 {
327 class UndoTextCommand : public KUndo2Command
328 {
329 public:
330 UndoTextCommand(QTextDocument *document, KUndo2Command *parent = 0)
331 : KUndo2Command(kundo2_i18n("Text"), parent),
332 m_document(document)
333 {}
334
335 void undo() {
336 QTextDocument *doc = const_cast<QTextDocument*>(m_document.data());
337 if (doc)
338 doc->undo(KoTextDocument(doc).textEditor()->cursor());
339 }
340
341 void redo() {
342 QTextDocument *doc = const_cast<QTextDocument*>(m_document.data());
343 if (doc)
344 doc->redo(KoTextDocument(doc).textEditor()->cursor());
345 }
346
347 QWeakPointer<QTextDocument> m_document;
348 };
349
350 if (command->id() != id())
351 return false;
352
353 ChangeTrackedDeleteCommand *other = const_cast<ChangeTrackedDeleteCommand *>(static_cast<const ChangeTrackedDeleteCommand *>(command));
354
355 if (other->m_canMerge == false)
356 return false;
357
358 if (other->m_removedElements.contains(m_addedChangeElement)) {
359 removeChangeElement(m_addedChangeElement);
360 other->m_removedElements.removeAll(m_addedChangeElement);
361 m_addedChangeElement = other->m_addedChangeElement;
362
363 m_removedElements += other->m_removedElements;
364 other->m_removedElements.clear();
365
366 m_newListIds = other->m_newListIds;
367
368 m_position = other->m_position;
369 m_length = other->m_length;
370
371 for(int i=0; i < command->childCount(); i++) {
372 new UndoTextCommand(m_document.data(), this);
373 }
374
375 return true;
376 }
377 return false;
378 }
379
updateListIds(QTextCursor & cursor)380 void ChangeTrackedDeleteCommand::updateListIds(QTextCursor &cursor)
381 {
382 if (m_document.isNull()) return;
383
384 m_newListIds.clear();
385 QTextCursor tempCursor(m_document.data());
386 QTextBlock startBlock = m_document.data()->findBlock(cursor.anchor());
387 QTextBlock endBlock = m_document.data()->findBlock(cursor.position());
388 QTextList *currentList;
389
390 for (QTextBlock currentBlock = startBlock; currentBlock != endBlock.next(); currentBlock = currentBlock.next()) {
391 tempCursor.setPosition(currentBlock.position());
392 currentList = tempCursor.currentList();
393 if (currentList) {
394 KoListStyle::ListIdType listId = ListId(currentList->format());
395 m_newListIds.push_back(listId);
396 }
397 }
398 }
updateListChanges()399 void ChangeTrackedDeleteCommand::updateListChanges()
400 {
401 if (m_document.isNull()) return;
402
403 QTextCursor tempCursor(m_document.data());
404 QTextBlock startBlock = m_document.data()->findBlock(m_position);
405 QTextBlock endBlock = m_document.data()->findBlock(m_position + m_length);
406 QTextList *currentList;
407 int newListIdsCounter = 0;
408
409 for (QTextBlock currentBlock = startBlock; currentBlock != endBlock.next(); currentBlock = currentBlock.next()) {
410 tempCursor.setPosition(currentBlock.position());
411 currentList = tempCursor.currentList();
412 if (currentList) {
413 KoListStyle::ListIdType listId = m_newListIds[newListIdsCounter];
414 if (!KoTextDocument(m_document).list(currentBlock)) {
415 KoList *list = KoTextDocument(m_document).list(listId);
416 if (list)
417 list->updateStoredList(currentBlock);
418 }
419 newListIdsCounter++;
420 }
421 }
422 }
423
~ChangeTrackedDeleteCommand()424 ChangeTrackedDeleteCommand::~ChangeTrackedDeleteCommand()
425 {
426 if (m_undone) {
427 removeChangeElement(m_addedChangeElement);
428 } else {
429 foreach (int changeId, m_removedElements) {
430 removeChangeElement(changeId);
431 }
432 }
433 }
434
removeChangeElement(int changeId)435 void ChangeTrackedDeleteCommand::removeChangeElement(int changeId)
436 {
437 KoTextDocument textDocument(m_document);
438 KoChangeTrackerElement *element = textDocument.changeTracker()->elementById(changeId);
439 KoTextDocument(m_document).changeTracker()->removeById(changeId);
440 }
441