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%", "%message%"); @@ -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