From 8d6a78665b79a382dc1871852ed7bd150263db79 Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Mon, 2 May 2011 19:38:42 +0100
Subject: Warn when sending corrections without support.


diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 513b446..f4aa745 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -12,16 +12,16 @@
 #include <Swift/Controllers/Intl.h>
 #include <Swiften/Base/format.h>
 #include <Swiften/Base/Algorithm.h>
-#include "Swiften/Avatars/AvatarManager.h"
-#include "Swiften/Chat/ChatStateNotifier.h"
-#include "Swiften/Chat/ChatStateTracker.h"
-#include "Swiften/Client/StanzaChannel.h"
-#include "Swiften/Disco/EntityCapsProvider.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
-#include "Swiften/Client/NickResolver.h"
-#include "Swift/Controllers/XMPPEvents/EventController.h"
+#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Chat/ChatStateNotifier.h>
+#include <Swiften/Chat/ChatStateTracker.h>
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swiften/Client/NickResolver.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
 #include <Swift/Controllers/StatusUtil.h>
+#include <Swiften/Disco/EntityCapsProvider.h>
 
 namespace Swift {
 	
@@ -29,7 +29,7 @@ namespace Swift {
  * The controller does not gain ownership of the stanzaChannel, nor the factory.
  */
 ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider)
-	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory) {
+	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider) {
 	isInMUC_ = isInMUC;
 	lastWasPresence_ = false;
 	chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider);
@@ -60,6 +60,7 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ
 	chatWindow_->addSystemMessage(startMessage);
 	chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
 	chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
+	handleBareJIDCapsChanged(toJID_);
 
 }
 
@@ -75,6 +76,19 @@ ChatController::~ChatController() {
 	delete chatStateTracker_;
 }
 
+void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) {
+	DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_);
+	if (disco) {
+		if (disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) {
+			chatWindow_->setCorrectionEnabled(ChatWindow::Yes);
+		} else {
+			chatWindow_->setCorrectionEnabled(ChatWindow::No);
+		}
+	} else {
+		chatWindow_->setCorrectionEnabled(ChatWindow::Maybe);
+	}
+}
+
 void ChatController::setToJID(const JID& jid) {
 	chatStateNotifier_->setContact(jid);
 	ChatControllerBase::setToJID(jid);
@@ -85,6 +99,7 @@ void ChatController::setToJID(const JID& jid) {
 		presence = jid.isBare() ? presenceOracle_->getHighestPriorityPresence(jid.toBare()) : presenceOracle_->getLastPresence(jid);
 	}
 	chatStateNotifier_->setContactIsOnline(presence && presence->getType() == Presence::Available);
+	handleBareJIDCapsChanged(toJID_);
 }
 
 bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) {
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 4fafb44..f6b8763 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -35,6 +35,7 @@ namespace Swift {
 			void handleStanzaAcked(boost::shared_ptr<Stanza> stanza);
 			void dayTicked() {lastWasPresence_ = false;}
 			void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/);
+			void handleBareJIDCapsChanged(const JID& jid);
 
 		private:
 			NickResolver* nickResolver_;
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 5e3c45f..802a7cb 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -21,6 +21,7 @@
 #include "Swiften/Elements/Delay.h"
 #include "Swiften/Base/foreach.h"
 #include "Swift/Controllers/XMPPEvents/EventController.h"
+#include "Swiften/Disco/EntityCapsProvider.h"
 #include "Swift/Controllers/UIInterfaces/ChatWindow.h"
 #include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
 #include "Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h"
@@ -28,10 +29,11 @@
 
 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) {
+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, EntityCapsProvider* entityCapsProvider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider) {
 	chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);
 	chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
 	chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
+	entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1));
 	setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable());
 	createDayChangeTimer();
 }
@@ -40,6 +42,12 @@ ChatControllerBase::~ChatControllerBase() {
 	delete chatWindow_;
 }
 
+void ChatControllerBase::handleCapsChanged(const JID& jid) {
+	if (jid.compare(toJID_, JID::WithoutResource) == 0) {
+		handleBareJIDCapsChanged(jid);
+	}
+}
+
 void ChatControllerBase::createDayChangeTimer() {
 	if (timerFactory_) {
 		boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index 86c1ef2..79d376c 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -35,6 +35,7 @@ namespace Swift {
 	class AvatarManager;
 	class UIEventStream;
 	class EventController;
+	class EntityCapsProvider;
 
 	class ChatControllerBase : public boost::bsignals::trackable {
 		public:
@@ -52,8 +53,9 @@ namespace Swift {
 			boost::signal<void ()> onUnreadCountChanged;
 			int getUnreadCount();
 			const JID& getToJID() {return toJID_;}
+			void handleCapsChanged(const JID& jid);
 		protected:
-			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);
+			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, EntityCapsProvider* entityCapsProvider);
 
 			/**
 			 * Pass the Message appended, and the stanza used to send it.
@@ -67,6 +69,7 @@ namespace Swift {
 			virtual bool isFromContact(const JID& from);
 			virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const = 0;
 			virtual void dayTicked() {};
+			virtual void handleBareJIDCapsChanged(const JID& jid) = 0;
 
 		private:
 			IDGenerator idGenerator_;
@@ -94,5 +97,6 @@ namespace Swift {
 			EventController* eventController_;
 			boost::shared_ptr<Timer> dateChangeTimer_;
 			TimerFactory* timerFactory_;
+			EntityCapsProvider* entityCapsProvider_;
 	};
 }
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 8b8b993..56f7a4c 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -480,7 +480,7 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional
 	} else {
 		std::string nick = nickMaybe ? nickMaybe.get() : jid_.getNode();
 		MUC::ref muc = mucManager->createMUC(mucJID);
-		MUCController* controller = new MUCController(jid_, muc, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_);
+		MUCController* controller = new MUCController(jid_, muc, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_);
 		mucControllers_[mucJID] = controller;
 		controller->setAvailableServerFeatures(serverDiscoInfo_);
 		controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller));
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index 93ceb13..aa0a1e7 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -30,6 +30,7 @@
 #include <Swift/Controllers/Roster/Roster.h>
 #include <Swift/Controllers/Roster/SetAvatar.h>
 #include <Swift/Controllers/Roster/SetPresence.h>
+#include <Swiften/Disco/EntityCapsProvider.h>
 
 
 #define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000
@@ -51,8 +52,9 @@ MUCController::MUCController (
 		UIEventStream* uiEventStream,
 		bool useDelayForLatency,
 		TimerFactory* timerFactory,
-		EventController* eventController) :
-			ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory), muc_(muc), nick_(nick), desiredNick_(nick) {
+		EventController* eventController,
+		EntityCapsProvider* entityCapsProvider) :
+			ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider), muc_(muc), nick_(nick), desiredNick_(nick) {
 	parting_ = true;
 	joined_ = false;
 	lastWasPresence_ = false;
@@ -82,6 +84,7 @@ MUCController::MUCController (
 	if (avatarManager_ != NULL) {
 		avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1)));
 	} 
+	handleBareJIDCapsChanged(muc->getJID());
 }
 
 MUCController::~MUCController() {
@@ -94,6 +97,23 @@ MUCController::~MUCController() {
 	delete completer_;
 }
 
+void MUCController::handleBareJIDCapsChanged(const JID& /*jid*/) {
+	ChatWindow::Tristate support = ChatWindow::Yes;
+	bool any = false;
+	foreach (const std::string& nick, currentOccupants_) {
+		DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_.toBare().toString() + "/" + nick);
+		if (disco && disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) {
+			any = true;
+		} else {
+			support = ChatWindow::Maybe;
+		}
+	}
+	if (!any) {
+		support = ChatWindow::No;
+	}
+	chatWindow_->setCorrectionEnabled(support);
+}
+
 /**
  * Join the MUC if not already in it.
  */
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index c6901df..3a79920 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -41,7 +41,7 @@ namespace Swift {
 
 	class MUCController : public ChatControllerBase {
 		public:
-			MUCController(const JID& self, MUC::ref muc, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController);
+			MUCController(const JID& self, MUC::ref muc, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider);
 			~MUCController();
 			boost::signal<void ()> onUserLeft;
 			boost::signal<void ()> onUserJoined;
@@ -83,6 +83,7 @@ namespace Swift {
 			bool shouldUpdateJoinParts();
 			void dayTicked() {lastWasPresence_ = false;}
 			void processUserPart();
+			void handleBareJIDCapsChanged(const JID& jid);
 
 		private:
 			MUC::ref muc_;
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index 5f5e44d..ad5ceac 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -25,6 +25,7 @@
 #include "Swiften/Presence/PresenceOracle.h"
 #include "Swiften/Network/TimerFactory.h"
 #include "Swiften/Elements/MUCUserPayload.h"
+#include "Swiften/Disco/DummyEntityCapsProvider.h"
 
 using namespace Swift;
 
@@ -56,12 +57,14 @@ public:
 		TimerFactory* timerFactory = NULL;
 		window_ = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
 		mucRegistry_ = new MUCRegistry();
+		entityCapsProvider_ = new DummyEntityCapsProvider();
 		muc_ = MUC::ref(new MUC(stanzaChannel_, iqRouter_, directedPresenceSender_, JID("teaparty@rooms.wonderland.lit"), mucRegistry_));
 		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
-		controller_ = new MUCController (self_, muc_, nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_);
+		controller_ = new MUCController (self_, muc_, nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_);
 	};
 
 	void tearDown() {
+		delete entityCapsProvider_;
 		delete controller_;
 		delete eventController_;
 		delete presenceOracle_;
@@ -240,6 +243,7 @@ private:
 	UIEventStream* uiEventStream_;
 	MockChatWindow* window_;
 	MUCRegistry* mucRegistry_;
+	DummyEntityCapsProvider* entityCapsProvider_;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest);
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index 92bec85..2690343 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -288,6 +288,7 @@ void MainController::handleConnected() {
 		discoInfo.addIdentity(DiscoInfo::Identity(CLIENT_NAME, "client", "pc"));
 		discoInfo.addFeature(DiscoInfo::ChatStatesFeature);
 		discoInfo.addFeature(DiscoInfo::SecurityLabelsFeature);
+		discoInfo.addFeature(DiscoInfo::MessageCorrectionFeature);
 		client_->getDiscoManager()->setCapsNode(CLIENT_NODE);
 		client_->getDiscoManager()->setDiscoInfo(discoInfo);
 
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index f65328d..e84116d 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -26,6 +26,7 @@ namespace Swift {
 	class ChatWindow {
 		public:
 			enum AckState {Pending, Received, Failed};
+			enum Tristate {Yes, No, Maybe};
 			ChatWindow() {}
 			virtual ~ChatWindow() {};
 
@@ -48,6 +49,7 @@ namespace Swift {
 			virtual void activate() = 0;
 			virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) = 0;
 			virtual void setSecurityLabelsEnabled(bool enabled) = 0;
+			virtual void setCorrectionEnabled(Tristate enabled) = 0;
 			virtual void setUnreadMessageCount(int count) = 0;
 			virtual void convertToMUC() = 0;
 //			virtual TreeWidget *getTreeWidget() = 0;
@@ -59,6 +61,16 @@ namespace Swift {
 			virtual void replaceLastMessage(const std::string& message) = 0;
 			virtual void setAckState(const std::string& id, AckState state) = 0;
 			virtual void flash() = 0;
+			/**
+			 * Set an alert on the window.
+			 * @param alertText Description of alert (required).
+			 * @param buttonText Button text to use (optional, no button is shown if empty).
+			 */
+			virtual void setAlert(const std::string& alertText, const std::string& buttonText = "") = 0;
+			/**
+			 * Removes an alert.
+			 */
+			virtual void cancelAlert() = 0;
 
 			boost::signal<void ()> onClosed;
 			boost::signal<void ()> onAllMessagesRead;
@@ -66,6 +78,7 @@ namespace Swift {
 			boost::signal<void ()> onSendCorrectionMessageRequest;
 			boost::signal<void ()> onUserTyping;
 			boost::signal<void ()> onUserCancelsTyping;
+			boost::signal<void ()> onAlertButtonClicked;
 	};
 }
 #endif
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index a5765fd..7d6828f 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -37,6 +37,9 @@ namespace Swift {
 			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() {};
+			virtual void setAlert(const std::string& /*alertText*/, const std::string& /*buttonText*/) {};
+			virtual void cancelAlert() {};
+			virtual void setCorrectionEnabled(Tristate /*enabled*/) {}
 
 			boost::signal<void ()> onClosed;
 			boost::signal<void ()> onAllMessagesRead;
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index 32c3067..c7a6fa8 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -30,6 +30,7 @@
 #include <QTextEdit>
 #include <QTime>
 #include <QUrl>
+#include <QPushButton>
 
 namespace Swift {
 QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageWasSystem_(false), previousMessageWasPresence_(false), eventStream_(eventStream) {
@@ -38,6 +39,7 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
 	completer_ = NULL;
 	theme_ = theme;
 	isCorrection_ = false;
+	correctionEnabled_ = Maybe;
 	updateTitleWithUnreadCount();
 	QtSettingsProvider settings;
 
@@ -45,10 +47,24 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
 	layout->setContentsMargins(0,0,0,0);
 	layout->setSpacing(2);
 
+	alertWidget_ = new QWidget(this);
+	QHBoxLayout* alertLayout = new QHBoxLayout(alertWidget_);
+	layout->addWidget(alertWidget_);
+	alertLabel_ = new QLabel(this);
+	alertLayout->addWidget(alertLabel_);
+	alertButton_ = new QPushButton(this);
+	connect (alertButton_, SIGNAL(clicked()), this, SLOT(handleAlertButtonClicked()));
+	alertLayout->addWidget(alertButton_);
+	QPalette palette = alertWidget_->palette();
+	palette.setColor(QPalette::Window, QColor(Qt::yellow));
+	palette.setColor(QPalette::WindowText, QColor(Qt::black));
+	alertWidget_->setPalette(palette);
+	alertLabel_->setPalette(palette);
+	alertWidget_->hide();
+
 	logRosterSplitter_ = new QSplitter(this);
 	logRosterSplitter_->setAutoFillBackground(true);
 	layout->addWidget(logRosterSplitter_);
-
 	messageLog_ = new QtChatView(theme, this);
 	logRosterSplitter_->addWidget(messageLog_);
 
@@ -104,10 +120,30 @@ QtChatWindow::~QtChatWindow() {
 
 }
 
+
 void QtChatWindow::handleFontResized(int fontSizeSteps) {
 	messageLog_->resizeFont(fontSizeSteps);
 }
 
+void QtChatWindow::handleAlertButtonClicked() {
+	onAlertButtonClicked();
+}
+
+void QtChatWindow::setAlert(const std::string& alertText, const std::string& buttonText) {
+	alertLabel_->setText(alertText.c_str());
+	if (buttonText.empty()) {
+		alertButton_->hide();
+	} else {
+		alertButton_->setText(buttonText.c_str());
+		alertButton_->show();
+	}
+	alertWidget_->show();
+}
+
+void QtChatWindow::cancelAlert() {
+	alertWidget_->hide();
+}
+
 void QtChatWindow::setTabComplete(TabComplete* completer) {
 	completer_ = completer;
 }
@@ -146,6 +182,11 @@ void QtChatWindow::handleKeyPressEvent(QKeyEvent* event) {
 }
 
 void QtChatWindow::beginCorrection() {
+	if (correctionEnabled_ == ChatWindow::Maybe) {
+		setAlert(Q2PSTRING(tr("This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message")));
+	} else if (correctionEnabled_ == ChatWindow::No) {
+		setAlert(Q2PSTRING(tr("This chat does not support message correction.  If you send a correction anyway, it will appear as a duplicate message")));
+	}
 	QTextCursor cursor = input_->textCursor();
 	cursor.select(QTextCursor::Document);
 	cursor.beginEditBlock();
@@ -156,6 +197,7 @@ void QtChatWindow::beginCorrection() {
 }
 
 void QtChatWindow::cancelCorrection() {
+	cancelAlert();
 	QTextCursor cursor = input_->textCursor();
 	cursor.select(QTextCursor::Document);
 	cursor.removeSelectedText();
@@ -237,6 +279,10 @@ void QtChatWindow::setSecurityLabelsEnabled(bool enabled) {
 	}
 }
 
+void QtChatWindow::setCorrectionEnabled(Tristate enabled) {
+	correctionEnabled_ = enabled;
+}
+
 SecurityLabelsCatalog::Item QtChatWindow::getSelectedSecurityLabel() {
 	assert(labelsWidget_->isEnabled());
 	return availableLabels_[labelsWidget_->currentIndex()];
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index 78d8f91..bc2d821 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -19,6 +19,7 @@ class QLineEdit;
 class QComboBox;
 class QLabel;
 class QSplitter;
+class QPushButton;
 
 namespace Swift {
 	class QtChatView;
@@ -63,6 +64,9 @@ namespace Swift {
 		public slots:
 			void handleChangeSplitterState(QByteArray state);
 			void handleFontResized(int fontSizeSteps);
+			void setAlert(const std::string& alertText, const std::string& buttonText = "");
+			void cancelAlert();
+			void setCorrectionEnabled(Tristate enabled);
 
 		signals:
 			void geometryChanged();
@@ -83,6 +87,7 @@ namespace Swift {
 			void handleInputChanged();
 			void handleKeyPressEvent(QKeyEvent* event);
 			void handleSplitterMoved(int pos, int index);
+			void handleAlertButtonClicked();
 
 		private:
 			void updateTitleWithUnreadCount();
@@ -102,6 +107,9 @@ namespace Swift {
 			QComboBox* labelsWidget_;
 			QtTreeWidget* treeWidget_;
 			QLabel* correctingLabel_;
+			QLabel* alertLabel_;
+			QWidget* alertWidget_;
+			QPushButton* alertButton_;
 			TabComplete* completer_;
 			std::vector<SecurityLabelsCatalog::Item> availableLabels_;
 			bool isCorrection_;
@@ -114,5 +122,6 @@ namespace Swift {
 			bool inputEnabled_;
 			IDGenerator id_;
 			QSplitter *logRosterSplitter_;
+			Tristate correctionEnabled_;
 	};
 }
diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp
index d6afb2d..f54b6bf 100644
--- a/Swiften/Elements/DiscoInfo.cpp
+++ b/Swiften/Elements/DiscoInfo.cpp
@@ -15,6 +15,8 @@ const std::string DiscoInfo::SecurityLabelsFeature = std::string("urn:xmpp:sec-l
 const std::string DiscoInfo::SecurityLabelsCatalogFeature = std::string("urn:xmpp:sec-label:catalog:2");
 const std::string DiscoInfo::JabberSearchFeature = std::string("jabber:iq:search");
 const std::string DiscoInfo::CommandsFeature = std::string("http://jabber.org/protocol/commands");
+const std::string DiscoInfo::MessageCorrectionFeature = std::string("urn:xmpp:message-correct:0");
+
 
 
 bool DiscoInfo::Identity::operator<(const Identity& other) const {
diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h
index ab09d05..c5c9e1c 100644
--- a/Swiften/Elements/DiscoInfo.h
+++ b/Swiften/Elements/DiscoInfo.h
@@ -22,6 +22,7 @@ namespace Swift {
 			static const std::string SecurityLabelsCatalogFeature;
 			static const std::string JabberSearchFeature;
 			static const std::string CommandsFeature;
+			static const std::string MessageCorrectionFeature;
 
 			class Identity {
 				public:
-- 
cgit v0.10.2-6-g49f6