From 5a334fd9b676564a8915baad312d92bd86358eec Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Tue, 16 Feb 2010 09:05:37 +0000
Subject: Preliminary Chat State Notifications support.

Only covers Active and Composing (Which is very possibly all we care about).

diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index e4031f2..8d63d99 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -3,6 +3,9 @@
 #include <boost/bind.hpp>
 
 #include "Swiften/Avatars/AvatarManager.h"
+#include "Swiften/Chat/ChatStateNotifier.h"
+#include "Swiften/Chat/ChatStateMessageSender.h"
+#include "Swiften/Chat/ChatStateTracker.h"
 #include "Swift/Controllers/UIInterfaces/ChatWindow.h"
 #include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
 #include "Swift/Controllers/NickResolver.h"
@@ -14,9 +17,20 @@ namespace Swift {
  */
 ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager)
  : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager) {
+	chatStateNotifier_ = new ChatStateNotifier();
+	chatStateMessageSender_ = new ChatStateMessageSender(chatStateNotifier_, stanzaChannel, contact);
+	chatStateTracker_ = new ChatStateTracker();
 	nickResolver_ = nickResolver;
 	presenceOracle_->onPresenceChange.connect(boost::bind(&ChatController::handlePresenceChange, this, _1, _2));
+	chatStateTracker_->onChatStateChange.connect(boost::bind(&ChatWindow::setContactChatState, chatWindow_, _1));
 	chatWindow_->setName(nickResolver_->jidToNick(toJID_));
+	chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
+	chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
+}
+
+void ChatController::setToJID(const JID& jid) {
+	chatStateMessageSender_->setContact(jid);
+	ChatControllerBase::setToJID(jid);
 }
 
 bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) {
@@ -30,10 +44,19 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<Message> message
 			toJID_ = from;
 		}
 	}
+	chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>());
+	chatStateTracker_->handleMessageReceived(message);
+}
+
+void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) {
+	if (chatStateNotifier_->contactShouldReceiveStates()) {
+		message->addPayload(boost::shared_ptr<Payload>(new ChatState(ChatState::Active)));
+	}
 }
 
 void ChatController::postSendMessage(const String& body) {
 	addMessage(body, "me", true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel() : boost::optional<SecurityLabel>(), String(avatarManager_->getAvatarPath(selfJID_).string()));
+	chatStateNotifier_->userSentMessage();
 }
 
 String ChatController::senderDisplayNameFromMessage(const JID& from) {
@@ -62,6 +85,7 @@ void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresenc
 	if (!(toJID_.isBare() && newPresence->getFrom().equals(toJID_, JID::WithoutResource)) && newPresence->getFrom() != toJID_) {
 		return;
 	}
+	chatStateTracker_->handlePresenceChange(newPresence, previousPresence);
 	String newStatusChangeString = getStatusChangeString(newPresence);
 	if (!previousPresence || newStatusChangeString != getStatusChangeString(previousPresence)) {
 		chatWindow_->addSystemMessage(newStatusChangeString);
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 58ea797..f2963fd 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -5,10 +5,14 @@
 
 namespace Swift {
 	class AvatarManager;
+	class ChatStateNotifier;
+	class ChatStateMessageSender;
+	class ChatStateTracker;
 	class NickResolver;
 	class ChatController : public ChatControllerBase {
 		public:
 			ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager*);
+			virtual void setToJID(const JID& jid);
 
 		private:
 			void handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> previousPresence);
@@ -16,11 +20,15 @@ namespace Swift {
 			bool isIncomingMessageFromMe(boost::shared_ptr<Message> message);
 			void postSendMessage(const String &body);
 			void preHandleIncomingMessage(boost::shared_ptr<Message> message);
+			void preSendMessageRequest(boost::shared_ptr<Message>);
 			String senderDisplayNameFromMessage(const JID& from);
 
 		private:
 			NickResolver* nickResolver_;
 			JID contact_;
+			ChatStateNotifier* chatStateNotifier_;
+			ChatStateMessageSender* chatStateMessageSender_;
+			ChatStateTracker* chatStateTracker_;
 	};
 }
 #endif
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 5f78795..6fe50d3 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -98,7 +98,9 @@ void ChatControllerBase::addMessage(const String& message, const String& senderN
 }
 
 void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
-	unreadMessages_.push_back(messageEvent);
+	if (messageEvent->isReadable()) {
+		unreadMessages_.push_back(messageEvent);
+	}
 	chatWindow_->setUnreadMessageCount(unreadMessages_.size());
 
 	boost::shared_ptr<Message> message = messageEvent->getStanza();
@@ -109,6 +111,9 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
 		chatWindow_->addErrorMessage(errorMessage);
 	}
 	else {
+		if (!messageEvent->isReadable()) {
+			return;
+		}
 		showChatWindow();
 		boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>();
 		boost::optional<SecurityLabel> maybeLabel = label ? boost::optional<SecurityLabel>(*label) : boost::optional<SecurityLabel>();
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index abf0116..e0d6ac5 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -32,7 +32,7 @@ namespace Swift {
 			void handleIncomingMessage(boost::shared_ptr<MessageEvent> message);
 			void addMessage(const String& message, const String& senderName, bool senderIsSelf, const boost::optional<SecurityLabel>& label, const String& avatarPath);
 			void setEnabled(bool enabled);
-			void setToJID(const JID& jid) {toJID_ = jid;};
+			virtual void setToJID(const JID& jid) {toJID_ = jid;};
 		protected:
 			ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager);
 
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 0efd3e1..9a72f1f 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -133,9 +133,9 @@ void ChatsManager::handleJoinMUCRequest(const JID &muc, const String &nick) {
 void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) {
 	JID jid = message->getFrom();
 	boost::shared_ptr<MessageEvent> event(new MessageEvent(message));
-	if (!event->isReadable()) {
-		return;
-	}
+	//if (!event->isReadable()) {
+	//	return;
+	//}
 
 	// Try to deliver it to a MUC
 	if (message->getType() == Message::Groupchat || message->getType() == Message::Error) {
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index 59c3152..5b38ba2 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -8,6 +8,7 @@
 
 #include "Swiften/Base/String.h"
 #include "Swiften/Elements/SecurityLabel.h"
+#include "Swiften/Elements/ChatState.h"
 
 namespace Swift {
 	class AvatarManager;
@@ -23,6 +24,7 @@ namespace Swift {
 			virtual void addSystemMessage(const String& message) = 0;
 			virtual void addErrorMessage(const String& message) = 0;
 
+			virtual void setContactChatState(ChatState::ChatStateType state) = 0;
 			virtual void setName(const String& name) = 0;
 			virtual void show() = 0;
 			virtual void activate() = 0;
@@ -38,6 +40,8 @@ namespace Swift {
 			boost::signal<void ()> onClosed;
 			boost::signal<void ()> onAllMessagesRead;
 			boost::signal<void (const String&)> onSendMessageRequest;
+			boost::signal<void ()> onUserTyping;
+			boost::signal<void ()> onUserCancelsTyping;
 	};
 }
 #endif
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index dc744cd..b487f50 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -13,6 +13,7 @@ namespace Swift {
 			virtual void addSystemMessage(const String& /*message*/) {};
 			virtual void addErrorMessage(const String& /*message*/) {};
 
+			virtual void setContactChatState(ChatState::ChatStateType /*state*/) {};
 			virtual void setName(const String& name) {name_ = name;};
 			virtual void show() {};
 			virtual void activate() {};
diff --git a/Swift/QtUI/QtChatTabs.cpp b/Swift/QtUI/QtChatTabs.cpp
index 6e5c55d..53fa5ce 100644
--- a/Swift/QtUI/QtChatTabs.cpp
+++ b/Swift/QtUI/QtChatTabs.cpp
@@ -104,7 +104,13 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) {
 		return;
 	}
 	tabs_->setTabText(index, widget->windowTitle());
-	tabs_->tabBar()->setTabTextColor(index, tabbable->isWidgetAlerting() ? QColor(255,0,0) : QColor(-1,-1,-1)); //invalid resets to default
+	QColor tabTextColor;
+	switch (tabbable->getWidgetAlertState()) {
+	case QtTabbable::WaitingActivity : tabTextColor = QColor(255, 0, 0); break;
+	case QtTabbable::ImpendingActivity : tabTextColor = QColor(0, 255, 0); break;
+	default : tabTextColor = QColor(-1,-1,-1);//invalid resets to default
+	}
+	tabs_->tabBar()->setTabTextColor(index, tabTextColor); 
 	if (widget == tabs_->currentWidget()) {
 		setWindowTitle(widget->windowTitle());
 	}
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index e982b21..6765e8a 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -56,7 +56,11 @@ QtChatWindow::QtChatWindow(const QString &contact, QtTreeWidgetFactory *treeWidg
 	input_->setAcceptRichText(false);
 	layout->addWidget(input_);
 	
+	inputClearing_ = false;
+	contactIsTyping_ = false;
+
 	connect(input_, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
+	connect(input_, SIGNAL(textChanged()), this, SLOT(handleInputChanged()));
 	setFocusProxy(input_);
 	connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(qAppFocusChanged(QWidget*, QWidget*)));
 
@@ -134,8 +138,20 @@ void QtChatWindow::setUnreadMessageCount(int count) {
 	updateTitleWithUnreadCount();
 }
 
-bool QtChatWindow::isWidgetAlerting() {
-	return unreadCount_ > 0;
+void QtChatWindow::setContactChatState(ChatState::ChatStateType state) {
+	contactIsTyping_ = (state == ChatState::Composing);
+	printf("Hay, composing %d, %d!\n", state, contactIsTyping_);
+	emit titleUpdated();
+}
+
+QtTabbable::AlertType QtChatWindow::getWidgetAlertState() {
+	if (contactIsTyping_) {
+		return ImpendingActivity;
+	} 
+	if (unreadCount_ > 0) {
+		return WaitingActivity;
+	}
+	return NoActivity;
 }
 
 void QtChatWindow::setName(const String& name) {
@@ -205,7 +221,20 @@ void QtChatWindow::addSystemMessage(const String& message) {
 void QtChatWindow::returnPressed() {
 	onSendMessageRequest(Q2PSTRING(input_->toPlainText()));
 	messageLog_->scrollToBottom();
+	inputClearing_ = true;
 	input_->clear();
+	inputClearing_ = false;
+}
+
+void QtChatWindow::handleInputChanged() {
+	if (inputClearing_) {
+		return;
+	}
+	if (input_->toPlainText().isEmpty()) {
+		onUserCancelsTyping();
+	} else {
+		onUserTyping();
+	}
 }
 
 void QtChatWindow::show() {
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index e147fb8..bde91e1 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -35,7 +35,8 @@ namespace Swift {
 			SecurityLabel getSelectedSecurityLabel();
 			void setName(const String& name);
 			void setInputEnabled(bool enabled);
-			virtual bool isWidgetAlerting();
+			QtTabbable::AlertType getWidgetAlertState();
+			void setContactChatState(ChatState::ChatStateType state);
 
 		protected slots:
 			void qAppFocusChanged(QWidget* old, QWidget* now);
@@ -45,11 +46,13 @@ namespace Swift {
 			void showEvent(QShowEvent* event);
 		private slots:
 			void returnPressed();
+			void handleInputChanged();
 
 		private:
 			void updateTitleWithUnreadCount();
 
 			int unreadCount_;
+			bool contactIsTyping_;
 			QString contact_;
 			QtChatView *messageLog_;
 			QtTextEdit* input_;
@@ -59,6 +62,7 @@ namespace Swift {
 			bool previousMessageWasSelf_;
 			bool previousMessageWasSystem_;
 			QString previousSenderName_;
+			bool inputClearing_;
 	};
 }
 
diff --git a/Swift/QtUI/QtTabbable.h b/Swift/QtUI/QtTabbable.h
index be528ce..cce45db 100644
--- a/Swift/QtUI/QtTabbable.h
+++ b/Swift/QtUI/QtTabbable.h
@@ -7,9 +7,10 @@ namespace Swift {
 	class QtTabbable : public QWidget {
 		Q_OBJECT
 		public:
+			enum AlertType {NoActivity, WaitingActivity, ImpendingActivity};
 			~QtTabbable();
 			bool isWidgetSelected();
-			virtual bool isWidgetAlerting() {return false;};
+			virtual AlertType getWidgetAlertState() {return NoActivity;};
 		protected:
 			QtTabbable() : QWidget() {};
 
diff --git a/Swiften/Chat/ChatStateMessageSender.cpp b/Swiften/Chat/ChatStateMessageSender.cpp
index d053cb5..ad1495f 100644
--- a/Swiften/Chat/ChatStateMessageSender.cpp
+++ b/Swiften/Chat/ChatStateMessageSender.cpp
@@ -1,5 +1,22 @@
 #include "Swiften/Chat/ChatStateMessageSender.h"
 
+#include <boost/bind.hpp>
+
+#include "Swiften/Client/StanzaChannel.h"
+
 namespace Swift {
 
+ChatStateMessageSender::ChatStateMessageSender(ChatStateNotifier* notifier, StanzaChannel* stanzaChannel, const JID& contact) : contact_(contact) {
+	notifier_ = notifier;
+	stanzaChannel_ = stanzaChannel;
+	notifier_->onChatStateChanged.connect(boost::bind(&ChatStateMessageSender::handleChatStateChanged, this, _1));
+}
+
+void ChatStateMessageSender::handleChatStateChanged(ChatState::ChatStateType state) {
+	boost::shared_ptr<Message> message(new Message());
+	message->setTo(contact_);
+	message->addPayload(boost::shared_ptr<Payload>(new ChatState(state)));
+	stanzaChannel_->sendMessage(message);
+}
+
 }
diff --git a/Swiften/Chat/ChatStateMessageSender.h b/Swiften/Chat/ChatStateMessageSender.h
index d27973d..aff0791 100644
--- a/Swiften/Chat/ChatStateMessageSender.h
+++ b/Swiften/Chat/ChatStateMessageSender.h
@@ -1,7 +1,20 @@
 #pragma once
 
+#include "Swiften/Chat/ChatStateNotifier.h"
+#include "Swiften/Elements/ChatState.h"
+#include "Swiften/JID/JID.h"
+
 namespace Swift {
+	class StanzaChannel;
 	class ChatStateMessageSender {
-		
+		public:
+			ChatStateMessageSender(ChatStateNotifier* notifier, StanzaChannel* stanzaChannel, const JID& contact);
+			void setContact(const JID& contact) {contact_ = contact;};
+
+		private:
+			void handleChatStateChanged(ChatState::ChatStateType state);
+			ChatStateNotifier* notifier_;
+			StanzaChannel* stanzaChannel_;
+			JID contact_;
 	};
 }
diff --git a/Swiften/Chat/ChatStateNotifier.cpp b/Swiften/Chat/ChatStateNotifier.cpp
index 432f708..7c6560f 100644
--- a/Swiften/Chat/ChatStateNotifier.cpp
+++ b/Swiften/Chat/ChatStateNotifier.cpp
@@ -16,7 +16,7 @@ void ChatStateNotifier::setContactHas85Caps(bool hasCaps) {
 void ChatStateNotifier::setUserIsTyping() {
 	if (contactShouldReceiveStates() && !userIsTyping_) {
 		userIsTyping_ = true;
-		onChatStateChanged(Composing);
+		onChatStateChanged(ChatState::Composing);
 	}
 }
 
@@ -27,7 +27,7 @@ void ChatStateNotifier::userSentMessage() {
 void ChatStateNotifier::userCancelledNewMessage() {
 	if (userIsTyping_) {
 		userIsTyping_ = false;
-		onChatStateChanged(Active);
+		onChatStateChanged(ChatState::Active);
 	}
 }
 
diff --git a/Swiften/Chat/ChatStateNotifier.h b/Swiften/Chat/ChatStateNotifier.h
index 90228b7..0ef4255 100644
--- a/Swiften/Chat/ChatStateNotifier.h
+++ b/Swiften/Chat/ChatStateNotifier.h
@@ -3,10 +3,11 @@
 #include <boost/signals.hpp>
 #include <boost/shared_ptr.hpp>
 
+#include "Swiften/Elements/ChatState.h"
+
 namespace Swift {
 	class ChatStateNotifier {
 		public:
-			enum ChatState {Active, Composing, Paused, Inactive, Gone};
 			ChatStateNotifier();
 			void setContactHas85Caps(bool hasCaps);
 			void setUserIsTyping();
@@ -15,7 +16,7 @@ namespace Swift {
 			void receivedMessageFromContact(bool hasActiveElement);
 			bool contactShouldReceiveStates();
 
-			boost::signal<void (ChatState)> onChatStateChanged;
+			boost::signal<void (ChatState::ChatStateType)> onChatStateChanged;
 		private:
 			bool contactHas85Caps_;
 			bool isInConversation_;
diff --git a/Swiften/Chat/ChatStateTracker.cpp b/Swiften/Chat/ChatStateTracker.cpp
index 553d2f4..94e01eb 100644
--- a/Swiften/Chat/ChatStateTracker.cpp
+++ b/Swiften/Chat/ChatStateTracker.cpp
@@ -1,5 +1,29 @@
 #include "Swiften/Chat/ChatStateTracker.h"
 
 namespace Swift {
+ChatStateTracker::ChatStateTracker() {
+	currentState_ = ChatState::Gone;
+}
+
+void ChatStateTracker::handleMessageReceived(boost::shared_ptr<Message> message) {
+	boost::shared_ptr<ChatState> statePayload = message->getPayload<ChatState>();
+	if (statePayload) {
+		changeState(statePayload->getChatState());;
+	}
+}
+
+void ChatStateTracker::handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence>) {
+	if (newPresence->getType() == Presence::Unavailable) {
+		onChatStateChange(ChatState::Gone);
+	}
+}
+
+void ChatStateTracker::changeState(ChatState::ChatStateType state) {
+	printf("Comparing state %d to old state %d\n", state, currentState_);
+	if (state != currentState_) {
+		currentState_ = state;
+		onChatStateChange(state);
+	}
+}
 
 }
diff --git a/Swiften/Chat/ChatStateTracker.h b/Swiften/Chat/ChatStateTracker.h
index 005c479..e612852 100644
--- a/Swiften/Chat/ChatStateTracker.h
+++ b/Swiften/Chat/ChatStateTracker.h
@@ -1,7 +1,21 @@
 #pragma once
 
+#include <boost/signal.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Elements/Message.h"
+#include "Swiften/Elements/Presence.h"
+#include "Swiften/ELements/ChatState.h"
+
 namespace Swift {
 	class ChatStateTracker {
-		
+		public:
+			ChatStateTracker();
+			void handleMessageReceived(boost::shared_ptr<Message> message);
+			void handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> oldPresence);
+			boost::signal<void (ChatState::ChatStateType)> onChatStateChange;
+		private:
+			void changeState(ChatState::ChatStateType state);
+			ChatState::ChatStateType currentState_;
 	};
 }
diff --git a/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp b/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp
index bacfc3a..44ec9d8 100644
--- a/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp
+++ b/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp
@@ -18,15 +18,15 @@ public:
 
 	int composingCallCount;
 	int activeCallCount;
-	ChatStateNotifier::ChatState currentState;
+	ChatState::ChatStateType currentState;
 
 private:
-	void handleChatStateChanged(ChatStateNotifier::ChatState newState) {
+	void handleChatStateChanged(ChatState::ChatStateType newState) {
 		switch (newState) {
-			case ChatStateNotifier::Composing:
+			case ChatState::Composing:
 				composingCallCount++;
 				break;
-			case ChatStateNotifier::Active:
+			case ChatState::Active:
 				activeCallCount++;
 				break;
 			default:
@@ -86,7 +86,7 @@ public:
 		notifier_->userCancelledNewMessage();
 		CPPUNIT_ASSERT_EQUAL(1, monitor_->composingCallCount);
 		CPPUNIT_ASSERT_EQUAL(1, monitor_->activeCallCount);
-		CPPUNIT_ASSERT_EQUAL(ChatStateNotifier::Active, monitor_->currentState);
+		CPPUNIT_ASSERT_EQUAL(ChatState::Active, monitor_->currentState);
 	}
 
 
diff --git a/Swiften/Elements/ChatState.h b/Swiften/Elements/ChatState.h
new file mode 100644
index 0000000..3378cc3
--- /dev/null
+++ b/Swiften/Elements/ChatState.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "Swiften/Base/String.h"
+#include "Swiften/Elements/Payload.h"
+
+namespace Swift {
+	class ChatState : public Payload {
+		public:
+			enum ChatStateType {Active, Composing, Paused, Inactive, Gone};
+			ChatState(ChatStateType state = Active) {
+				state_ = state;
+			}
+
+			ChatStateType getChatState() { return state_; }
+			void setChatState(ChatStateType state) {state_ = state;}
+
+		private:
+			ChatStateType state_;
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/ChatStateParser.cpp b/Swiften/Parser/PayloadParsers/ChatStateParser.cpp
new file mode 100644
index 0000000..52d860a
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/ChatStateParser.cpp
@@ -0,0 +1,35 @@
+#include "Swiften/Parser/PayloadParsers/ChatStateParser.h"
+
+namespace Swift {
+
+ChatStateParser::ChatStateParser() : level_(0) {
+}
+
+void ChatStateParser::handleStartElement(const String& element, const String&, const AttributeMap&) {
+	if (level_ == 0) {
+		ChatState::ChatStateType state = ChatState::Active;
+		if (element == "active") {
+			state = ChatState::Active;
+		} else if (element == "composing") {
+			state = ChatState::Composing;
+		} else if (element == "inactive") {
+			state = ChatState::Inactive;
+		} else if (element == "paused") {
+			state = ChatState::Paused;
+		} else if (element == "gone") {
+			state = ChatState::Gone;
+		}
+		getPayloadInternal()->setChatState(state);
+	}
+	++level_;
+}
+
+void ChatStateParser::handleEndElement(const String&, const String&) {
+	--level_;
+}
+
+void ChatStateParser::handleCharacterData(const String&) {
+
+}
+
+}
diff --git a/Swiften/Parser/PayloadParsers/ChatStateParser.h b/Swiften/Parser/PayloadParsers/ChatStateParser.h
new file mode 100644
index 0000000..cd212c0
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/ChatStateParser.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "Swiften/Elements/ChatState.h"
+#include "Swiften/Parser/GenericPayloadParser.h"
+
+namespace Swift {
+	class ChatStateParser : public GenericPayloadParser<ChatState> {
+		public:
+			ChatStateParser();
+
+			virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes);
+			virtual void handleEndElement(const String& element, const String&);
+			virtual void handleCharacterData(const String& data);
+
+		private:
+			int level_;
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/ChatStateParserFactory.h b/Swiften/Parser/PayloadParsers/ChatStateParserFactory.h
new file mode 100644
index 0000000..1582d09
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/ChatStateParserFactory.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "Swiften/Parser/PayloadParserFactory.h"
+#include "Swiften/Parser/PayloadParsers/ChatStateParser.h"
+
+namespace Swift {
+	class PayloadParserFactoryCollection;
+
+	class ChatStateParserFactory : public PayloadParserFactory {
+		public:
+			ChatStateParserFactory() {
+			}
+
+			virtual bool canParse(const String& element, const String& ns, const AttributeMap&) const {
+				return ns == "http://jabber.org/protocol/chatstates" && 
+					(element == "active" || element == "composing" 
+					 || element == "paused" || element == "inactive" || element == "gone");
+			}
+
+			virtual PayloadParser* createPayloadParser() {
+				return new ChatStateParser();
+			}
+
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index eb4cda0..0857f64 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -4,6 +4,7 @@
 #include "Swiften/Parser/PayloadParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/ErrorParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/BodyParserFactory.h"
+#include "Swiften/Parser/PayloadParsers/ChatStateParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/PriorityParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/ResourceBindParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/StartSessionParserFactory.h"
@@ -41,6 +42,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new VCardUpdateParserFactory()));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new VCardParserFactory()));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new PrivateStorageParserFactory(this)));
+	factories_.push_back(shared_ptr<PayloadParserFactory>(new ChatStateParserFactory()));
 	foreach(shared_ptr<PayloadParserFactory> factory, factories_) {
 		addFactory(factory.get());
 	}
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index 1e1dcd8..7d93d8b 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -18,6 +18,7 @@ sources = [
 		"PayloadParserFactory.cpp",
 		"PayloadParserFactoryCollection.cpp",
 		"PayloadParsers/BodyParser.cpp",
+		"PayloadParsers/ChatStateParser.cpp",
 		"PayloadParsers/DiscoInfoParser.cpp",
 		"PayloadParsers/ErrorParser.cpp",
 		"PayloadParsers/FullPayloadParserFactoryCollection.cpp",
diff --git a/Swiften/SConscript b/Swiften/SConscript
index e31818c..3f82bfe 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -55,6 +55,7 @@ sources = [
 		"Serializer/PayloadSerializer.cpp",
 		"Serializer/PayloadSerializerCollection.cpp",
 		"Serializer/PayloadSerializers/CapsInfoSerializer.cpp",
+		"Serializer/PayloadSerializers/ChatStateSerializer.cpp",
 		"Serializer/PayloadSerializers/DiscoInfoSerializer.cpp",
 		"Serializer/PayloadSerializers/ErrorSerializer.cpp",
 		"Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp",
diff --git a/Swiften/Serializer/PayloadSerializers/ChatStateSerializer.cpp b/Swiften/Serializer/PayloadSerializers/ChatStateSerializer.cpp
new file mode 100644
index 0000000..0507092
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/ChatStateSerializer.cpp
@@ -0,0 +1,22 @@
+#include "Swiften/Serializer/PayloadSerializers/ChatStateSerializer.h"
+
+namespace Swift {
+
+ChatStateSerializer::ChatStateSerializer() : GenericPayloadSerializer<ChatState>() {
+}
+
+String ChatStateSerializer::serializePayload(boost::shared_ptr<ChatState> chatState)  const {
+	String result("<");
+	switch (chatState->getChatState()) {
+		case ChatState::Active: result += "active"; break;
+		case ChatState::Composing: result += "composing"; break;
+		case ChatState::Paused: result += "paused"; break;
+		case ChatState::Inactive: result += "inactive"; break;
+		case ChatState::Gone: result += "gone"; break;
+		default: result += "gone"; break;
+	}
+	result += " xmlns=\"http://jabber.org/protocol/chatstates\"/>";
+	return result;
+}
+
+}
diff --git a/Swiften/Serializer/PayloadSerializers/ChatStateSerializer.h b/Swiften/Serializer/PayloadSerializers/ChatStateSerializer.h
new file mode 100644
index 0000000..e99b8b6
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/ChatStateSerializer.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "Swiften/Serializer/GenericPayloadSerializer.h"
+#include "Swiften/Elements/ChatState.h"
+
+namespace Swift {
+	class ChatStateSerializer : public GenericPayloadSerializer<ChatState> {
+		public:
+			ChatStateSerializer();
+
+			virtual String serializePayload(boost::shared_ptr<ChatState> error)  const;
+	};
+}
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index fc20018..accf6c6 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -2,6 +2,7 @@
 #include "Swiften/Base/foreach.h"
 #include "Swiften/Serializer/PayloadSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/BodySerializer.h"
+#include "Swiften/Serializer/PayloadSerializers/ChatStateSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/PrioritySerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/ErrorSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/RosterSerializer.h"
@@ -25,6 +26,7 @@ namespace Swift {
 
 FullPayloadSerializerCollection::FullPayloadSerializerCollection() {
 	serializers_.push_back(new BodySerializer());
+	serializers_.push_back(new ChatStateSerializer());
 	serializers_.push_back(new PrioritySerializer());
 	serializers_.push_back(new ErrorSerializer());
 	serializers_.push_back(new RosterSerializer());
-- 
cgit v0.10.2-6-g49f6