From 09f61b624e99f69a9221ba46c15efa61892f475f Mon Sep 17 00:00:00 2001
From: Vlad Voicu <vladvoic@gmail.com>
Date: Fri, 29 Apr 2011 21:13:05 +0300
Subject: Correct message feature using XEP-Correct

Uses Kev's not-yet-published protocol for correcting the last sent message.
Release-Notes: You can now correct your previously sent message in a chat by pressing 'up' in the input field.

License: This patch is BSD-licensed, see  http://www.opensource.org/licenses/bsd-license.php

diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 911f3e2..9767f8d 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -102,8 +102,23 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me
 			setToJID(from);
 		}
 	}
-	chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>());
+	boost::shared_ptr<Replace> replace = message->getPayload<Replace>();
+	if (replace) {
+		// Determine the timestamp
+		boost::posix_time::ptime timeStamp = boost::posix_time::microsec_clock::universal_time();
+		boost::optional<boost::posix_time::ptime> messageTimeStamp = getMessageTimestamp(message);
+		if (messageTimeStamp) {
+			timeStamp = *messageTimeStamp;
+		}
+		std::string body = message->getBody();
+		chatWindow_->replaceMessage(body, lastMessageUIID_, timeStamp);
+		replacedMessage_ = true;
+	}
+	else {
+		replacedMessage_ = false;
+	}
 	chatStateTracker_->handleMessageReceived(message);
+	chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>());
 }
 
 void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
@@ -116,10 +131,19 @@ void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) {
 }
 
 void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) {
-	std::string id = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time());
-	if (stanzaChannel_->getStreamManagementEnabled() && !id.empty()) {
-		chatWindow_->setAckState(id, ChatWindow::Pending);
-		unackedStanzas_[sentStanza] = id;
+	if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty()) {
+		chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending);
+		unackedStanzas_[sentStanza] = myLastMessageUIID_;
+	}
+	boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>();
+	if (replace) {
+		chatWindow_->replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time());
+	} else {
+		myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time());
+		if (stanzaChannel_->getStreamManagementEnabled()) {
+			chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending);
+			unackedStanzas_[sentStanza] = myLastMessageUIID_;
+		}
 	}
 	lastWasPresence_ = false;
 	chatStateNotifier_->userSentMessage();
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index dd4bf90..4fafb44 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -40,6 +40,7 @@ namespace Swift {
 			NickResolver* nickResolver_;
 			ChatStateNotifier* chatStateNotifier_;
 			ChatStateTracker* chatStateTracker_;
+			std::string myLastMessageUIID_;
 			bool isInMUC_;
 			bool lastWasPresence_;
 			std::string lastStatusChangeString_;
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 566d075..14e17cd 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -30,9 +30,10 @@ namespace Swift {
 ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory) {
 	chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);
 	chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
-	chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1));
+	chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
 	setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable());
 	createDayChangeTimer();
+	replacedMessage_ = false;
 }
 
 ChatControllerBase::~ChatControllerBase() {
@@ -88,7 +89,7 @@ void ChatControllerBase::handleAllMessagesRead() {
 	chatWindow_->setUnreadMessageCount(0);
 }
 
-void ChatControllerBase::handleSendMessageRequest(const std::string &body) {
+void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool isCorrectionMessage) {
 	if (!stanzaChannel_->isAvailable() || body.empty()) {
 		return;
 	}
@@ -104,6 +105,10 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body) {
 		boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
 		message->addPayload(boost::shared_ptr<Delay>(new Delay(now, selfJID_)));
 	}
+	if (isCorrectionMessage) {
+		message->addPayload(boost::shared_ptr<Replace> (new Replace(lastSentMessageStanzaID_)));
+	}
+	message->setID(lastSentMessageStanzaID_ = idGenerator_.generateID());
 	stanzaChannel_->sendMessage(message);
 	postSendMessage(message->getBody(), boost::dynamic_pointer_cast<Stanza>(message));
 	onActivity(message->getBody());
@@ -164,7 +169,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
 		JID from = message->getFrom();
 		std::vector<boost::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>();
 		for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) {
-			if (!delayPayloads[i]->getFrom()) { 
+			if (!delayPayloads[i]->getFrom()) {
 				continue;
 			}
 			boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
@@ -180,9 +185,10 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
 		if (messageTimeStamp) {
 			timeStamp = *messageTimeStamp;
 		}
-
-		addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp);
 		onActivity(body);
+		if (!replacedMessage_) {
+			lastMessageUIID_ = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp);
+		}
 	}
 	chatWindow_->show();
 	chatWindow_->setUnreadMessageCount(unreadMessages_.size());
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index 4898320..e0f1b94 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -26,6 +26,7 @@
 #include "Swiften/Elements/ErrorPayload.h"
 #include "Swiften/Presence/PresenceOracle.h"
 #include "Swiften/Queries/IQRouter.h"
+#include "Swiften/Base/IDGenerator.h"
 
 namespace Swift {
 	class IQRouter;
@@ -66,8 +67,10 @@ namespace Swift {
 			virtual void dayTicked() {};
 
 		private:
+			IDGenerator idGenerator_;
+			std::string lastSentMessageStanzaID_;
 			void createDayChangeTimer();
-			void handleSendMessageRequest(const std::string &body);
+			void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage);
 			void handleAllMessagesRead();
 			void handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error);
 			std::string getErrorMessage(boost::shared_ptr<ErrorPayload>);
@@ -82,6 +85,8 @@ namespace Swift {
 			ChatWindow* chatWindow_;
 			JID toJID_;
 			bool labelsEnabled_;
+			bool replacedMessage_;
+			std::string lastMessageUIID_;
 			PresenceOracle* presenceOracle_;
 			AvatarManager* avatarManager_;
 			bool useDelayForLatency_;
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index c7bcf1e..aa4416b 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -40,6 +40,7 @@ namespace Swift {
 			virtual void addSystemMessage(const std::string& message) = 0;
 			virtual void addPresenceMessage(const std::string& message) = 0;
 			virtual void addErrorMessage(const std::string& message) = 0;
+			virtual void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) = 0;
 
 			virtual void setContactChatState(ChatState::ChatStateType state) = 0;
 			virtual void setName(const std::string& name) = 0;
@@ -61,9 +62,11 @@ namespace Swift {
 
 			boost::signal<void ()> onClosed;
 			boost::signal<void ()> onAllMessagesRead;
-			boost::signal<void (const std::string&)> onSendMessageRequest;
+			boost::signal<void (const std::string&, bool isCorrection)> onSendMessageRequest;
+			boost::signal<void ()> onSendCorrectionMessageRequest;
 			boost::signal<void ()> onUserTyping;
 			boost::signal<void ()> onUserCancelsTyping;
+			boost::signal<void (bool correction)> onSendMessageCorrection;
 	};
 }
 #endif
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index 53a90a7..82c5b52 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -34,12 +34,13 @@ namespace Swift {
 			virtual void setRosterModel(Roster* /*roster*/) {};
 			virtual void setTabComplete(TabComplete*) {};
 			virtual void replaceLastMessage(const std::string&) {};
+			virtual void replaceMessage(const std::string&, const std::string&, const boost::posix_time::ptime&) {};
 			void setAckState(const std::string& /*id*/, AckState /*state*/) {};
 			virtual void flash() {};
 
 			boost::signal<void ()> onClosed;
 			boost::signal<void ()> onAllMessagesRead;
-			boost::signal<void (const std::string&)> onSendMessageRequest;
+			boost::signal<void (const std::string&, bool isCorrection)> onSendMessageRequest;
 
 			std::string name_;
 			std::string lastMessageBody_;
diff --git a/Swift/QtUI/ChatSnippet.h b/Swift/QtUI/ChatSnippet.h
index 3aa5fcc..7cbb3b3 100644
--- a/Swift/QtUI/ChatSnippet.h
+++ b/Swift/QtUI/ChatSnippet.h
@@ -17,7 +17,7 @@ namespace Swift {
 		public:
 			ChatSnippet(bool appendToPrevious);
 			virtual ~ChatSnippet();
-			
+
 			virtual const QString& getContent() const = 0;
 			virtual QString getContinuationElementID() const { return ""; }
 
@@ -26,7 +26,7 @@ namespace Swift {
 			bool getAppendToPrevious() const {
 				return appendToPrevious_;
 			}
-			
+
 			static QString escape(const QString& original) {
 				QString result(original);
 				result.replace("%message%", "&#37;message&#37;");
@@ -37,11 +37,12 @@ namespace Swift {
 				return result;
 			}
 
+			static QString timeToEscapedString(const QDateTime& time);
+
 		protected:
 			void setContinuationFallbackSnippet(boost::shared_ptr<ChatSnippet> continuationFallback) {
 				continuationFallback_ = continuationFallback;
 			}
-			static QString timeToEscapedString(const QDateTime& time);
 		private:
 			bool appendToPrevious_;
 			boost::shared_ptr<ChatSnippet> continuationFallback_;
diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp
index 4846af6..eab6d91 100644
--- a/Swift/QtUI/QtChatView.cpp
+++ b/Swift/QtUI/QtChatView.cpp
@@ -138,6 +138,25 @@ void QtChatView::replaceLastMessage(const QString& newMessage, const QString& no
 	replace.setInnerXml(ChatSnippet::escape(note));
 }
 
+QString QtChatView::getLastSentMessage() {
+	return lastElement_.toPlainText();
+}
+
+void QtChatView::replaceMessage(const QString& newMessage, const QString& id, const QDateTime& editTime) {
+	rememberScrolledToBottom();
+	QWebElement message = document_.findFirst("#" + id);
+	if (!message.isNull()) {
+		QWebElement replaceContent = message.findFirst("span.swift_message");
+		assert(!replaceContent.isNull());
+		QString old = replaceContent.toOuterXml();
+		replaceContent.setInnerXml(ChatSnippet::escape(newMessage));
+		QWebElement replaceTime = message.findFirst("span.swift_time");
+		assert(!replaceTime.isNull());
+		old = replaceTime.toOuterXml();
+		replaceTime.setInnerXml(ChatSnippet::escape(tr("%1 edited").arg(ChatSnippet::timeToEscapedString(editTime))));
+	}
+}
+
 void QtChatView::copySelectionToClipboard() {
 	if (!webPage_->selectedText().isEmpty()) {
 		webPage_->triggerAction(QWebPage::Copy);
diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h
index ce12ca8..32741f4 100644
--- a/Swift/QtUI/QtChatView.h
+++ b/Swift/QtUI/QtChatView.h
@@ -30,8 +30,10 @@ namespace Swift {
 			void addLastSeenLine();
 			void replaceLastMessage(const QString& newMessage);
 			void replaceLastMessage(const QString& newMessage, const QString& note);
+			void replaceMessage(const QString& newMessage, const QString& id, const QDateTime& time);
 			void rememberScrolledToBottom();
 			void setAckXML(const QString& id, const QString& xml);
+			QString getLastSentMessage();
 
 		signals:
 			void gotFocus();
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index 312ec65..ab230c4 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -35,6 +35,7 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
 	inputEnabled_ = true;
 	completer_ = NULL;
 	theme_ = theme;
+	isCorrection_ = false;
 	updateTitleWithUnreadCount();
 
 	QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
@@ -99,6 +100,7 @@ void QtChatWindow::setTabComplete(TabComplete* completer) {
 void QtChatWindow::handleKeyPressEvent(QKeyEvent* event) {
 	int key = event->key();
 	Qt::KeyboardModifiers modifiers = event->modifiers();
+	QTextCursor cursor;
 	if (key == Qt::Key_W && modifiers == Qt::ControlModifier) {
 		close();
 	} else if (
@@ -118,6 +120,17 @@ void QtChatWindow::handleKeyPressEvent(QKeyEvent* event) {
 		emit requestActiveTab();
 	} else if (key == Qt::Key_Tab) {
 		tabComplete();
+	} else if ((key == Qt::Key_Up) && input_->toPlainText().isEmpty() && !(lastSentMessage_.isEmpty())) {
+		cursor = input_->textCursor();
+		cursor.select(QTextCursor::Document);
+		cursor.beginEditBlock();
+		cursor.insertText(QString(lastSentMessage_));
+		cursor.endEditBlock();
+		isCorrection_ = true;
+	} else if (key == Qt::Key_Down && isCorrection_ && (cursor = input_->textCursor()).atBlockEnd()) {
+		cursor.select(QTextCursor::Document);
+		cursor.removeSelectedText();
+		isCorrection_ = false;
 	} else {
 		messageLog_->handleKeyPressEvent(event);
 	}
@@ -351,6 +364,15 @@ void QtChatWindow::addSystemMessage(const std::string& message) {
 	previousMessageWasPresence_ = false;
 }
 
+void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) {
+	if (!id.empty()) {
+		QString messageHTML(Qt::escape(P2QSTRING(message)));
+		messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML)));
+		messageHTML.replace("\n","<br/>");
+		messageLog_->replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time));
+	}
+}
+
 void QtChatWindow::addPresenceMessage(const std::string& message) {
 	if (isWidgetSelected()) {
 		onAllMessagesRead();
@@ -372,7 +394,9 @@ void QtChatWindow::returnPressed() {
 		return;
 	}
 	messageLog_->scrollToBottom();
-	onSendMessageRequest(Q2PSTRING(input_->toPlainText()));
+	lastSentMessage_ = QString(input_->toPlainText());
+	onSendMessageRequest(Q2PSTRING(input_->toPlainText()), isCorrection_);
+	isCorrection_ = false;
 	inputClearing_ = true;
 	input_->clear();
 	inputClearing_ = false;
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index 9e3aeb3..fd6b315 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -36,6 +36,7 @@ namespace Swift {
 			void addSystemMessage(const std::string& message);
 			void addPresenceMessage(const std::string& message);
 			void addErrorMessage(const std::string& errorMessage);
+			void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time);
 			void show();
 			void activate();
 			void setUnreadMessageCount(int count);
@@ -82,6 +83,7 @@ namespace Swift {
 			bool contactIsTyping_;
 			LastLineTracker lastLineTracker_;
 			QString contact_;
+			QString lastSentMessage_;
 			QtChatView* messageLog_;
 			QtChatTheme* theme_;
 			QtTextEdit* input_;
@@ -89,6 +91,7 @@ namespace Swift {
 			QtTreeWidget* treeWidget_;
 			TabComplete* completer_;
 			std::vector<SecurityLabelsCatalog::Item> availableLabels_;
+			bool isCorrection_;
 			bool previousMessageWasSelf_;
 			bool previousMessageWasSystem_;
 			bool previousMessageWasPresence_;
diff --git a/Swift/QtUI/QtTextEdit.cpp b/Swift/QtUI/QtTextEdit.cpp
index 79b3b8d..f7ac1ef 100644
--- a/Swift/QtUI/QtTextEdit.cpp
+++ b/Swift/QtUI/QtTextEdit.cpp
@@ -31,6 +31,8 @@ void QtTextEdit::keyPressEvent(QKeyEvent* event) {
 			   || (key == Qt::Key_Tab && modifiers == Qt::ControlModifier)
 			   || (key == Qt::Key_A && modifiers == Qt::AltModifier)
 			   || (key == Qt::Key_Tab)
+			   || (key == Qt::Key_Up)
+			   || (key == Qt::Key_Down)
 	) {
 		emit unhandledKeyPressEvent(event);
 	}
diff --git a/Swiften/Elements/Message.h b/Swiften/Elements/Message.h
index a553eb3..3623e73 100644
--- a/Swiften/Elements/Message.h
+++ b/Swiften/Elements/Message.h
@@ -14,6 +14,7 @@
 #include "Swiften/Elements/Subject.h"
 #include "Swiften/Elements/ErrorPayload.h"
 #include "Swiften/Elements/Stanza.h"
+#include "Swiften/Elements/Replace.h"
 
 namespace Swift {
 	class Message : public Stanza {
diff --git a/Swiften/Elements/Replace.h b/Swiften/Elements/Replace.h
new file mode 100644
index 0000000..dc8ff59
--- /dev/null
+++ b/Swiften/Elements/Replace.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2011 Vlad Voicu
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Elements/Payload.h"
+
+namespace Swift {
+	class Replace : public Payload {
+		public:
+			typedef boost::shared_ptr<Replace> ref;
+			Replace(std::string id = "") : replaceID_(id) {};
+			std::string getId() {
+				return replaceID_;
+			}
+			void setId(std::string id) {
+				replaceID_ = id;
+			}
+		private:
+			std::string replaceID_;
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index 5052e67..55deafc 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -40,6 +40,7 @@
 #include "Swiften/Parser/PayloadParsers/DelayParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/MUCUserPayloadParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/NicknameParserFactory.h"
+#include "Swiften/Parser/PayloadParsers/ReplaceParser.h"
 
 using namespace boost;
 
@@ -49,6 +50,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<IBBParser>("", "http://jabber.org/protocol/ibb")));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<StatusShowParser>("show")));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<StatusParser>("status")));
+	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<ReplaceParser>("replace", "http://swift.im/protocol/replace")));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<BodyParser>("body")));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<SubjectParser>("subject")));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<PriorityParser>("priority")));
diff --git a/Swiften/Parser/PayloadParsers/ReplaceParser.cpp b/Swiften/Parser/PayloadParsers/ReplaceParser.cpp
new file mode 100644
index 0000000..8e5659f
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/ReplaceParser.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2011 Vlad Voicu
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "Swiften/Parser/PayloadParsers/ReplaceParser.h"
+
+namespace Swift {
+
+	ReplaceParser::ReplaceParser() : level_(0) {
+	}
+
+	void ReplaceParser::handleStartElement(const std::string&, const std::string&, const AttributeMap& attributes) {
+		if (level_ == 0) {
+			std::string id = attributes.getAttribute("id");
+			getPayloadInternal()->setId(id);
+		}
+		level_++;
+	}
+
+	void ReplaceParser::handleEndElement(const std::string&, const std::string&) {
+		--level_;
+	}
+
+	void ReplaceParser::handleCharacterData(const std::string&) {
+	}
+
+}
diff --git a/Swiften/Parser/PayloadParsers/ReplaceParser.h b/Swiften/Parser/PayloadParsers/ReplaceParser.h
new file mode 100644
index 0000000..0789927
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/ReplaceParser.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2011 Vlad Voicu
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Elements/Replace.h"
+#include "Swiften/Parser/GenericPayloadParser.h"
+
+namespace Swift {
+	class ReplaceParser : public GenericPayloadParser<Replace> {
+		public:
+			ReplaceParser();
+			virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes);
+			virtual void handleEndElement(const std::string& element, const std::string&);
+			virtual void handleCharacterData(const std::string& data);
+
+		private:
+			int level_;
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/ReplaceTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/ReplaceTest.cpp
new file mode 100644
index 0000000..7c34eb1
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/UnitTest/ReplaceTest.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2011 Vlad Voicu
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include "Swiften/Parser/PayloadParsers/ReplaceParser.h"
+#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h"
+
+using namespace Swift;
+
+class ReplaceParserTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(ReplaceParserTest);
+		CPPUNIT_TEST(testParseTrivial);
+		CPPUNIT_TEST(testParseChild);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void testParseTrivial() {
+			PayloadsParserTester parser;
+			CPPUNIT_ASSERT(parser.parse("<replace id='bad1' xmlns='http://swift.im/protocol/replace'/>"));
+			Replace::ref payload = boost::dynamic_pointer_cast <Replace>(parser.getPayload());
+			CPPUNIT_ASSERT_EQUAL(std::string("bad1"), payload->getId());
+		}
+		void testParseChild() {
+			PayloadsParserTester parser;
+			CPPUNIT_ASSERT(parser.parse("<replace id='bad1' xmlns='http://swift.im/protocol/replace' ><child xmlns='blah' id=\"hi\"/></replace>"));
+			Replace::ref payload = boost::dynamic_pointer_cast <Replace>(parser.getPayload());
+			CPPUNIT_ASSERT_EQUAL(std::string("bad1"), payload->getId());
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ReplaceParserTest);
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index e7e8991..d9915ed 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -53,6 +53,7 @@ sources = [
 		"PayloadParsers/DelayParser.cpp",
 		"PayloadParsers/MUCUserPayloadParser.cpp",
 		"PayloadParsers/NicknameParser.cpp",
+		"PayloadParsers/ReplaceParser.cpp",
 		"PlatformXMLParserFactory.cpp",
 		"PresenceParser.cpp",
 		"SerializingParser.cpp",
diff --git a/Swiften/SConscript b/Swiften/SConscript
index b2dcdff..e474ae3 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -288,6 +288,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("Parser/PayloadParsers/UnitTest/StorageParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/PrivateStorageParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/VCardUpdateParserTest.cpp"),
+			File("Parser/PayloadParsers/UnitTest/ReplaceTest.cpp"),
 			File("Parser/UnitTest/AttributeMapTest.cpp"),
 			File("Parser/UnitTest/IQParserTest.cpp"),
 			File("Parser/UnitTest/MessageParserTest.cpp"),
@@ -331,6 +332,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("Serializer/PayloadSerializers/UnitTest/VCardSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/StorageSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/PrivateStorageSerializerTest.cpp"),
+			File("Serializer/PayloadSerializers/UnitTest/ReplaceSerializerTest.cpp"),
 			File("Serializer/UnitTest/StreamFeaturesSerializerTest.cpp"),
 			File("Serializer/UnitTest/AuthSuccessSerializerTest.cpp"),
 			File("Serializer/UnitTest/AuthChallengeSerializerTest.cpp"),
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index 0f05580..a1f412b 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -41,6 +41,7 @@
 #include "Swiften/Serializer/PayloadSerializers/InBandRegistrationPayloadSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/NicknameSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/SearchPayloadSerializer.h"
+#include "Swiften/Serializer/PayloadSerializers/ReplaceSerializer.h"
 
 namespace Swift {
 
@@ -79,6 +80,7 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() {
 	serializers_.push_back(new InBandRegistrationPayloadSerializer());
 	serializers_.push_back(new NicknameSerializer());
 	serializers_.push_back(new SearchPayloadSerializer());
+	serializers_.push_back(new ReplaceSerializer());
 	foreach(PayloadSerializer* serializer, serializers_) {
 		addSerializer(serializer);
 	}
diff --git a/Swiften/Serializer/PayloadSerializers/ReplaceSerializer.h b/Swiften/Serializer/PayloadSerializers/ReplaceSerializer.h
new file mode 100644
index 0000000..303b2b8
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/ReplaceSerializer.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2011 Vlad Voicu
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Serializer/GenericPayloadSerializer.h"
+#include "Swiften/Serializer/XML/XMLTextNode.h"
+#include "Swiften/Elements/Replace.h"
+
+namespace Swift {
+	class ReplaceSerializer : public GenericPayloadSerializer<Replace> {
+		public:
+			ReplaceSerializer() : GenericPayloadSerializer<Replace>() {}
+
+			virtual std::string serializePayload(boost::shared_ptr<Replace> replace) const {
+				return "<replace id = '" + replace->getId() + "' xmlns='http://swift.im/protocol/replace'/>";
+			}
+	};
+}
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/ReplaceSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/ReplaceSerializerTest.cpp
new file mode 100644
index 0000000..8fe96e5
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/ReplaceSerializerTest.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2011 Vlad Voicu
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include "Swiften/Serializer/PayloadSerializers/ReplaceSerializer.h"
+
+using namespace Swift;
+
+class ReplaceSerializerTest: public CppUnit::TestFixture {
+	CPPUNIT_TEST_SUITE(ReplaceSerializerTest);
+	CPPUNIT_TEST(testSerialize);
+	CPPUNIT_TEST_SUITE_END();
+
+	public:
+		ReplaceSerializerTest() {}
+
+		void testSerialize() {
+			ReplaceSerializer testling;
+			boost::shared_ptr<Replace> replace(new Replace());
+			replace->setId("bad1");
+			CPPUNIT_ASSERT_EQUAL(std::string("<replace id = 'bad1' xmlns='http://swift.im/protocol/replace'/>"), testling.serialize(replace));
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ReplaceSerializerTest);
-- 
cgit v0.10.2-6-g49f6