From fe8a8aa031da734f3112184420b372021d9e10c7 Mon Sep 17 00:00:00 2001
From: Mateusz Piekos <mateuszpiekos@gmail.com>
Date: Wed, 30 May 2012 14:40:27 +0200
Subject: Support whiteboarding.

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

diff --git a/BuildTools/SCons/SConscript.boot b/BuildTools/SCons/SConscript.boot
index 32ec818..b049f94 100644
--- a/BuildTools/SCons/SConscript.boot
+++ b/BuildTools/SCons/SConscript.boot
@@ -181,7 +181,7 @@ if not env["assertions"] :
 	env.Append(CPPDEFINES = ["NDEBUG"])
 
 if env["experimental"] :
-	env.Append(CPPDEFINES = ["SWIFT_EXPERIMENTAL_FT", "SWIFT_EXPERIMENTAL_HISTORY"])
+	env.Append(CPPDEFINES = ["SWIFT_EXPERIMENTAL_FT", "SWIFT_EXPERIMENTAL_HISTORY", "SWIFT_EXPERIMENTAL_WB"])
 
 # If we build shared libs on AMD64, we need -fPIC.
 # This should have no performance impact om AMD64
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index f40f704..16b22fe 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -26,6 +26,9 @@
 #include <Swiften/Base/foreach.h>
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
+#include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h>
+#include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h>
+#include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h>
 #include <Swiften/Elements/DeliveryReceipt.h>
 #include <Swiften/Elements/DeliveryReceiptRequest.h>
 #include <Swift/Controllers/SettingConstants.h>
@@ -73,6 +76,9 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ
 	chatWindow_->onFileTransferAccept.connect(boost::bind(&ChatController::handleFileTransferAccept, this, _1, _2));
 	chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1));
 	chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1));
+	chatWindow_->onWhiteboardSessionAccept.connect(boost::bind(&ChatController::handleWhiteboardSessionAccept, this));
+	chatWindow_->onWhiteboardSessionCancel.connect(boost::bind(&ChatController::handleWhiteboardSessionCancel, this));
+	chatWindow_->onWhiteboardWindowShow.connect(boost::bind(&ChatController::handleWhiteboardWindowShow, this));
 	handleBareJIDCapsChanged(toJID_);
 
 	settings_->onSettingChanged.connect(boost::bind(&ChatController::handleSettingChanged, this, _1));
@@ -255,6 +261,14 @@ void ChatController::handleNewFileTransferController(FileTransferController* ftc
 	ftControllers[ftID] = ftc;
 }
 
+void ChatController::handleWhiteboardSessionRequest(bool senderIsSelf) {
+	lastWbID_ = chatWindow_->addWhiteboardRequest(senderIsSelf);
+}
+
+void ChatController::handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state) {
+	chatWindow_->setWhiteboardSessionStatus(lastWbID_, state);
+}
+
 void ChatController::handleFileTransferCancel(std::string id) {
 	SWIFT_LOG(debug) << "handleFileTransferCancel(" << id << ")" << std::endl;
 	if (ftControllers.find(id) != ftControllers.end()) {
@@ -287,6 +301,18 @@ void ChatController::handleSendFileRequest(std::string filename) {
 	eventStream_->send(boost::make_shared<SendFileUIEvent>(getToJID(), filename));
 }
 
+void ChatController::handleWhiteboardSessionAccept() {
+	eventStream_->send(boost::make_shared<AcceptWhiteboardSessionUIEvent>(toJID_));
+}
+
+void ChatController::handleWhiteboardSessionCancel() {
+	eventStream_->send(boost::make_shared<CancelWhiteboardSessionUIEvent>(toJID_));
+}
+
+void ChatController::handleWhiteboardWindowShow() {
+	eventStream_->send(boost::make_shared<ShowWhiteboardUIEvent>(toJID_));
+}
+
 std::string ChatController::senderDisplayNameFromMessage(const JID& from) {
 	return nickResolver_->jidToNick(from);
 }
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index a873ae9..66ec37d 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -30,6 +30,8 @@ namespace Swift {
 			virtual void setToJID(const JID& jid);
 			virtual void setOnline(bool online);
 			virtual void handleNewFileTransferController(FileTransferController* ftc);
+			virtual void handleWhiteboardSessionRequest(bool senderIsSelf);
+			virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state);
 			virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/);
 
 		protected:
@@ -57,6 +59,10 @@ namespace Swift {
 			void handleFileTransferAccept(std::string /* id */, std::string /* filename */);
 			void handleSendFileRequest(std::string filename);
 
+			void handleWhiteboardSessionAccept();
+			void handleWhiteboardSessionCancel();
+			void handleWhiteboardWindowShow();
+
 			void handleSettingChanged(const std::string& settingPath);
 			void checkForDisplayingDisplayReceiptsAlert();
 
@@ -78,6 +84,7 @@ namespace Swift {
 			bool userWantsReceipts_;
 			std::map<std::string, FileTransferController*> ftControllers;
 			SettingsProvider* settings_;
+			std::string lastWbID_;
 	};
 }
 
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 6b51df6..1e0e9c2 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -42,6 +42,7 @@
 #include <Swift/Controllers/Settings/SettingsProvider.h>
 #include <Swift/Controllers/SettingConstants.h>
 #include <Swiften/Client/StanzaChannel.h>
+#include <Swift/Controllers/WhiteboardManager.h>
 
 namespace Swift {
 
@@ -72,7 +73,8 @@ ChatsManager::ChatsManager(
 		XMPPRoster* roster,
 		bool eagleMode,
 		SettingsProvider* settings,
-		HistoryController* historyController) :
+		HistoryController* historyController,
+		WhiteboardManager* whiteboardManager) :
 			jid_(jid), 
 			joinMUCWindowFactory_(joinMUCWindowFactory), 
 			useDelayForLatency_(useDelayForLatency), 
@@ -83,7 +85,8 @@ ChatsManager::ChatsManager(
 			roster_(roster),
 			eagleMode_(eagleMode),
 			settings_(settings),
-			historyController_(historyController) {
+			historyController_(historyController),
+			whiteboardManager_(whiteboardManager) {
 	timerFactory_ = timerFactory;
 	eventController_ = eventController;
 	stanzaChannel_ = stanzaChannel;
@@ -109,6 +112,10 @@ ChatsManager::ChatsManager(
 	mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, profileSettings_);
 	mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1));
 	ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1));
+	whiteboardManager_->onSessionRequest.connect(boost::bind(&ChatsManager::handleWhiteboardSessionRequest, this, _1, _2));
+	whiteboardManager_->onRequestAccepted.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardAccepted));
+	whiteboardManager_->onSessionTerminate.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardTerminated));
+	whiteboardManager_->onRequestRejected.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardRejected));
 	roster_->onJIDAdded.connect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1));
 	roster_->onJIDRemoved.connect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1));
 	roster_->onJIDUpdated.connect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1));
@@ -657,6 +664,29 @@ void ChatsManager::handleNewFileTransferController(FileTransferController* ftc)
 	chatController->activateChatWindow();
 }
 
+void ChatsManager::handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf) {
+	ChatController* chatController = getChatControllerOrCreate(contact);
+	chatController->handleWhiteboardSessionRequest(senderIsSelf);
+	chatController->activateChatWindow();
+}
+
+void ChatsManager::handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state) {
+	ChatController* chatController = getChatControllerOrCreate(contact);
+	chatController->handleWhiteboardStateChange(state);
+	chatController->activateChatWindow();
+	if (state == ChatWindow::WhiteboardAccepted) {
+		boost::filesystem::path path;
+		JID bareJID = contact.toBare();
+		if (avatarManager_) {
+			path = avatarManager_->getAvatarPath(bareJID);
+		}
+		ChatListWindow::Chat chat(bareJID, nickResolver_->jidToNick(bareJID), "", 0, StatusShow::None, path, false);
+ 		chatListWindow_->addWhiteboardSession(chat);
+	} else {
+		chatListWindow_->removeWhiteboardSession(contact.toBare());
+	}
+}
+
 void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) {
 	if (chat.isMUC) {
 		/* FIXME: This means that recents requiring passwords will just flat-out not work */
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 94efde1..5b8b785 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -18,6 +18,7 @@
 #include <Swiften/MUC/MUCRegistry.h>
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIInterfaces/ChatListWindow.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
 #include <Swiften/MUC/MUCBookmark.h>
 
 namespace Swift {
@@ -47,11 +48,12 @@ namespace Swift {
 	class FileTransferController;
 	class XMPPRoster;
 	class SettingsProvider;
+	class WhiteboardManager;
 	class HistoryController;
 	
 	class ChatsManager {
 		public:
-			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_);
+			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager);
 			virtual ~ChatsManager();
 			void setAvatarManager(AvatarManager* avatarManager);
 			void setOnline(bool enabled);
@@ -73,6 +75,8 @@ namespace Swift {
 			void handleBookmarksReady();
 			void handleChatActivity(const JID& jid, const std::string& activity, bool isMUC);
 			void handleNewFileTransferController(FileTransferController*);
+			void handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf);
+			void handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state);
 			void appendRecent(const ChatListWindow::Chat& chat);
 			void prependRecent(const ChatListWindow::Chat& chat);
 			void setupBookmarks();
@@ -131,5 +135,6 @@ namespace Swift {
 			bool userWantsReceipts_;
 			SettingsProvider* settings_;
 			HistoryController* historyController_;
+			WhiteboardManager* whiteboardManager_;
 	};
 }
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index 294dcb8..482b19c 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -17,6 +17,7 @@
 #include "Swift/Controllers/Settings/DummySettingsProvider.h"
 #include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
 #include "Swift/Controllers/UIInterfaces/ChatListWindowFactory.h"
+#include "Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h"
 #include "Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h"
 #include "Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h"
 #include "Swiften/Client/Client.h"
@@ -49,6 +50,8 @@
 #include "Swiften/Elements/DeliveryReceipt.h"
 #include <Swiften/Base/Algorithm.h>
 #include <Swift/Controllers/SettingConstants.h>
+#include <Swift/Controllers/WhiteboardManager.h>
+#include <Swiften/Whiteboard/WhiteboardSessionManager.h>
 
 using namespace Swift;
 
@@ -100,11 +103,13 @@ public:
 		chatListWindow_ = new MockChatListWindow();
 		ftManager_ = new DummyFileTransferManager();
 		ftOverview_ = new FileTransferOverview(ftManager_);
+		avatarManager_ = new NullAvatarManager();
+		wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsManager_);
+		wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_);
 
 		mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_);
-		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL);
+		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_);
 
-		avatarManager_ = new NullAvatarManager();
 		manager_->setAvatarManager(avatarManager_);
 	};
 	
@@ -115,6 +120,8 @@ public:
 		delete manager_;
 		delete ftOverview_;
 		delete ftManager_;
+		delete wbSessionManager_;
+		delete wbManager_;
 		delete directedPresenceSender_;
 		delete presenceSender_;
 		delete presenceOracle_;
@@ -460,6 +467,7 @@ private:
 	MockRepository* mocks_;
 	UIEventStream* uiEventStream_;
 	ChatListWindowFactory* chatListWindowFactory_;
+	WhiteboardWindowFactory* whiteboardWindowFactory_;
 	MUCSearchWindowFactory* mucSearchWindowFactory_;
 	MUCRegistry* mucRegistry_;
 	DirectedPresenceSender* directedPresenceSender_;
@@ -471,6 +479,8 @@ private:
 	ChatListWindow* chatListWindow_;
 	FileTransferOverview* ftOverview_;
 	FileTransferManager* ftManager_;
+	WhiteboardSessionManager* wbSessionManager_;
+	WhiteboardManager* wbManager_;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest);
diff --git a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h
index 6ac8d4a..5bbd490 100644
--- a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h
+++ b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h
@@ -16,6 +16,8 @@ namespace Swift {
 			virtual ~MockChatListWindow() {};
 			void addMUCBookmark(const MUCBookmark& /*bookmark*/) {}
 			void removeMUCBookmark(const MUCBookmark& /*bookmark*/) {}
+			void addWhiteboardSession(const ChatListWindow::Chat& /*chat*/) {};
+			void removeWhiteboardSession(const JID& /*jid*/) {};
 			void setBookmarksEnabled(bool /*enabled*/) {}
 			void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {}
 			void setUnreadCount(int /*unread*/) {}
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index f3a0226..c2a7b33 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -46,6 +46,7 @@
 #include "Swift/Controllers/PresenceNotifier.h"
 #include "Swift/Controllers/EventNotifier.h"
 #include "Swift/Controllers/Storages/StoragesFactory.h"
+#include "Swift/Controllers/WhiteboardManager.h"
 #include "SwifTools/Dock/Dock.h"
 #include "SwifTools/Notifier/TogglableNotifier.h"
 #include "Swiften/Base/foreach.h"
@@ -254,6 +255,8 @@ void MainController::resetClient() {
 	userSearchControllerAdd_ = NULL;
 	delete adHocManager_;
 	adHocManager_ = NULL;
+	delete whiteboardManager_;
+	whiteboardManager_ = NULL;
 	clientInitialized_ = false;
 }
 
@@ -303,6 +306,7 @@ void MainController::handleConnected() {
 		rosterController_->getWindow()->onShowCertificateRequest.connect(boost::bind(&MainController::handleShowCertificateRequest, this));
 
 		contactEditController_ = new ContactEditController(rosterController_, client_->getVCardManager(), uiFactory_, uiEventStream_);
+		whiteboardManager_ = new WhiteboardManager(uiFactory_, uiEventStream_, client_->getNickResolver(), client_->getWhiteboardSessionManager());
 
 		/* Doing this early as an ordering fix. Various things later will
 		 * want to have the user's nick available and this means it will
@@ -312,9 +316,9 @@ void MainController::handleConnected() {
 #ifdef SWIFT_EXPERIMENTAL_HISTORY
 		historyController_ = new HistoryController(storages_->getHistoryStorage());
 		historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_);
-		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_);
+		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_);
 #else
-		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL);
+		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_);
 #endif
 		
 		client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1));
@@ -335,6 +339,9 @@ void MainController::handleConnected() {
 		discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature);
 		discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature);
 #endif
+#ifdef SWIFT_EXPERIMENTAL_WB
+		discoInfo.addFeature(DiscoInfo::WhiteboardFeature);
+#endif
 		discoInfo.addFeature(DiscoInfo::MessageDeliveryReceiptsFeature);
 		client_->getDiscoManager()->setCapsNode(CLIENT_NODE);
 		client_->getDiscoManager()->setDiscoInfo(discoInfo);
@@ -342,6 +349,7 @@ void MainController::handleConnected() {
 		userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_);
 		userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_);
 		adHocManager_ = new AdHocManager(JID(boundJID_.getDomain()), uiFactory_, client_->getIQRouter(), uiEventStream_, rosterController_->getWindow());
+		
 	}
 	loginWindow_->setIsLoggingIn(false);
 
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index 6f7482e..2e5bd05 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -70,6 +70,7 @@ namespace Swift {
 	class AdHocManager;
 	class AdHocCommandWindowFactory;
 	class FileTransferOverview;
+	class WhiteboardManager;
 
 	class MainController {
 		public:
@@ -174,5 +175,6 @@ namespace Swift {
 			bool offlineRequested_;
 			static const int SecondsToWaitBeforeForceQuitting;
 			FileTransferOverview* ftOverview_;
+			WhiteboardManager* whiteboardManager_;
 	};
 }
diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h
index 9932dc4..8389a44 100644
--- a/Swift/Controllers/Roster/ContactRosterItem.h
+++ b/Swift/Controllers/Roster/ContactRosterItem.h
@@ -25,6 +25,7 @@ class ContactRosterItem : public RosterItem {
 	public:
 		enum Feature {
 			FileTransferFeature,
+			WhiteboardFeature,
 		};
 		
 	public:
diff --git a/Swift/Controllers/Roster/RosterController.cpp b/Swift/Controllers/Roster/RosterController.cpp
index 170bfd0..ec52993 100644
--- a/Swift/Controllers/Roster/RosterController.cpp
+++ b/Swift/Controllers/Roster/RosterController.cpp
@@ -321,6 +321,9 @@ void RosterController::handleOnCapsChanged(const JID& jid) {
 		if (info->hasFeature(DiscoInfo::JingleFeature) && info->hasFeature(DiscoInfo::JingleFTFeature) && info->hasFeature(DiscoInfo::JingleTransportsIBBFeature)) {
 			features.insert(ContactRosterItem::FileTransferFeature);
 		}
+		if (info->hasFeature(DiscoInfo::WhiteboardFeature)) {
+			features.insert(ContactRosterItem::WhiteboardFeature);
+		}
 		roster_->setAvailableFeatures(jid, features);
 	}
 }
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index b6f81b3..7cd017b 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -73,7 +73,8 @@ if env["SCONS_STAGE"] == "build" :
 			"Translator.cpp",
 			"XMPPURIController.cpp",
 			"ChatMessageSummarizer.cpp",
-			"SettingConstants.cpp"
+			"SettingConstants.cpp",
+			"WhiteboardManager.cpp"
 		])
 
 	env.Append(UNITTEST_SOURCES = [
diff --git a/Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h b/Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h
new file mode 100644
index 0000000..93cad03
--- /dev/null
+++ b/Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/JID/JID.h>
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+namespace Swift {
+	class AcceptWhiteboardSessionUIEvent : public UIEvent {
+		typedef boost::shared_ptr<AcceptWhiteboardSessionUIEvent> ref;
+	public:
+		AcceptWhiteboardSessionUIEvent(const JID& jid) : jid_(jid) {}
+		const JID& getContact() const {return jid_;}
+	private:
+		JID jid_;
+	};
+}
diff --git a/Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h b/Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h
new file mode 100644
index 0000000..f5c3b0e
--- /dev/null
+++ b/Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/JID/JID.h>
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+namespace Swift {
+	class CancelWhiteboardSessionUIEvent : public UIEvent {
+		typedef boost::shared_ptr<CancelWhiteboardSessionUIEvent> ref;
+	public:
+		CancelWhiteboardSessionUIEvent(const JID& jid) : jid_(jid) {}
+		const JID& getContact() const {return jid_;}
+	private:
+		JID jid_;
+	};
+}
diff --git a/Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h b/Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h
new file mode 100644
index 0000000..f5b995b
--- /dev/null
+++ b/Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/JID/JID.h"
+
+#include "Swift/Controllers/UIEvents/UIEvent.h"
+
+namespace Swift {
+	class RequestWhiteboardUIEvent : public UIEvent {
+	public:
+		RequestWhiteboardUIEvent(const JID& contact) : contact_(contact) {};
+		const JID& getContact() const {return contact_;}
+	private:
+		JID contact_;
+	};
+}
diff --git a/Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h b/Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h
new file mode 100644
index 0000000..265bf7d
--- /dev/null
+++ b/Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/JID/JID.h"
+
+#include "Swift/Controllers/UIEvents/UIEvent.h"
+
+namespace Swift {
+	class ShowWhiteboardUIEvent : public UIEvent {
+	public:
+		ShowWhiteboardUIEvent(const JID& contact) : contact_(contact) {};
+		const JID& getContact() const {return contact_;}
+	private:
+		JID contact_;
+	};
+}
+
diff --git a/Swift/Controllers/UIInterfaces/ChatListWindow.h b/Swift/Controllers/UIInterfaces/ChatListWindow.h
index d047f8c..cb55bb3 100644
--- a/Swift/Controllers/UIInterfaces/ChatListWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatListWindow.h
@@ -48,6 +48,8 @@ namespace Swift {
 
 			virtual void setBookmarksEnabled(bool enabled) = 0;
 			virtual void addMUCBookmark(const MUCBookmark& bookmark) = 0;
+			virtual void addWhiteboardSession(const ChatListWindow::Chat& chat) = 0;
+			virtual void removeWhiteboardSession(const JID& jid) = 0;
 			virtual void removeMUCBookmark(const MUCBookmark& bookmark) = 0;
 			virtual void setRecents(const std::list<Chat>& recents) = 0;
 			virtual void setUnreadCount(int unread) = 0;
@@ -55,6 +57,7 @@ namespace Swift {
 
 			boost::signal<void (const MUCBookmark&)> onMUCBookmarkActivated;
 			boost::signal<void (const Chat&)> onRecentActivated;
+			boost::signal<void (const JID&)> onWhiteboardActivated;
 			boost::signal<void ()> onClearRecentsRequested;
 	};
 }
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index 9188c7f..5db1a54 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -37,6 +37,7 @@ namespace Swift {
 			enum OccupantAction {Kick, Ban, MakeModerator, MakeParticipant, MakeVisitor, AddContact};
 			enum RoomAction {ChangeSubject, Configure, Affiliations, Destroy, Invite};
 			enum FileTransferState {WaitingForAccept, Negotiating, Transferring, Canceled, Finished, FTFailed};
+			enum WhiteboardSessionState {WhiteboardAccepted, WhiteboardTerminated, WhiteboardRejected};
 			ChatWindow() {}
 			virtual ~ChatWindow() {};
 
@@ -60,6 +61,9 @@ namespace Swift {
 			virtual void setFileTransferStatus(std::string, const FileTransferState state, const std::string& msg = "") = 0;
 			virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true) = 0;
 
+			virtual std::string addWhiteboardRequest(bool senderIsSelf) = 0;
+			virtual void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) = 0;
+
 			// message receipts
 			virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) = 0;
 
@@ -132,6 +136,11 @@ namespace Swift {
 			boost::signal<void (std::string /* id */, std::string /* description */)> onFileTransferStart;
 			boost::signal<void (std::string /* id */, std::string /* path */)> onFileTransferAccept;
 			boost::signal<void (std::string /* path */)> onSendFileRequest;
+
+			//Whiteboard related	
+			boost::signal<void ()> onWhiteboardSessionAccept;
+			boost::signal<void ()> onWhiteboardSessionCancel;
+			boost::signal<void ()> onWhiteboardWindowShow;
 	};
 }
 
diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h
index d6bea77..6b4efd8 100644
--- a/Swift/Controllers/UIInterfaces/UIFactory.h
+++ b/Swift/Controllers/UIInterfaces/UIFactory.h
@@ -20,6 +20,7 @@
 #include <Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h>
+#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h>
 
 namespace Swift {
 	class UIFactory : 
@@ -36,7 +37,8 @@ namespace Swift {
 			public ProfileWindowFactory,
 			public ContactEditWindowFactory,
 			public AdHocCommandWindowFactory,
-			public FileTransferListWidgetFactory {
+			public FileTransferListWidgetFactory,
+			public WhiteboardWindowFactory {
 		public:
 			virtual ~UIFactory() {}
 	};
diff --git a/Swift/Controllers/UIInterfaces/WhiteboardWindow.h b/Swift/Controllers/UIInterfaces/WhiteboardWindow.h
new file mode 100644
index 0000000..a4a9ef0
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/WhiteboardWindow.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Base/boost_bsignals.h"
+
+#include <string>
+
+namespace Swift {
+	class WhiteboardSession;
+	class WhiteboardElement;
+
+	class WhiteboardWindow {
+	public:
+		virtual ~WhiteboardWindow() {}
+
+		virtual void show() = 0;
+		virtual void setSession(boost::shared_ptr<WhiteboardSession> session) = 0;
+		virtual void activateWindow() = 0;
+		virtual void setName(const std::string& name) = 0;
+	};
+}
diff --git a/Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h b/Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h
new file mode 100644
index 0000000..c2d2f6c
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+namespace Swift {
+	class WhiteboardSession;
+	class WhiteboardWindow;
+
+	class WhiteboardWindowFactory {
+	public :
+		virtual ~WhiteboardWindowFactory() {};
+
+		virtual WhiteboardWindow* createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession) = 0;
+	};
+}
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index dbfef3e..998a4eb 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -52,6 +52,10 @@ namespace Swift {
 			void setSubject(const std::string& /*subject*/) {}
 			virtual void showRoomConfigurationForm(Form::ref) {}
 			virtual void addMUCInvitation(const std::string& /*senderName*/, const JID& /*jid*/, const std::string& /*reason*/, const std::string& /*password*/, bool = true) {};
+
+			virtual std::string addWhiteboardRequest(bool) {return "";};
+			virtual void setWhiteboardSessionStatus(std::string, const ChatWindow::WhiteboardSessionState){};
+
 			virtual void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&) {}
 			virtual void setAvailableRoomActions(const std::vector<RoomAction> &) {};
 			virtual InviteToChatWindow* createInviteToChatWindow() {return NULL;}
diff --git a/Swift/Controllers/WhiteboardManager.cpp b/Swift/Controllers/WhiteboardManager.cpp
new file mode 100644
index 0000000..50aba6f
--- /dev/null
+++ b/Swift/Controllers/WhiteboardManager.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/Controllers/WhiteboardManager.h>
+
+#include <boost/bind.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h>
+#include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h>
+#include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h>
+#include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h>
+#include "Swiften/Client/NickResolver.h"
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swiften/Whiteboard/WhiteboardSessionManager.h>
+
+namespace Swift {
+	typedef std::pair<JID, WhiteboardWindow*> JIDWhiteboardWindowPair;
+
+	WhiteboardManager::WhiteboardManager(WhiteboardWindowFactory* whiteboardWindowFactory, UIEventStream* uiEventStream, NickResolver* nickResolver, WhiteboardSessionManager* whiteboardSessionManager) : whiteboardWindowFactory_(whiteboardWindowFactory), uiEventStream_(uiEventStream), nickResolver_(nickResolver), whiteboardSessionManager_(whiteboardSessionManager) {
+
+#ifdef SWIFT_EXPERIMENTAL_WB
+		whiteboardSessionManager_->onSessionRequest.connect(boost::bind(&WhiteboardManager::handleIncomingSession, this, _1));
+#endif
+		uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&WhiteboardManager::handleUIEvent, this, _1));
+	}
+
+	WhiteboardManager::~WhiteboardManager() {
+		foreach (JIDWhiteboardWindowPair whiteboardWindowPair, whiteboardWindows_) {
+			delete whiteboardWindowPair.second;
+		}
+	}
+
+	WhiteboardWindow* WhiteboardManager::createNewWhiteboardWindow(const JID& contact, WhiteboardSession::ref session) {
+		WhiteboardWindow *window = whiteboardWindowFactory_->createWhiteboardWindow(session);
+		window->setName(nickResolver_->jidToNick(contact));
+		whiteboardWindows_[contact.toBare()] = window;
+		return window;
+	}
+
+	WhiteboardWindow* WhiteboardManager::findWhiteboardWindow(const JID& contact) {
+		if (whiteboardWindows_.find(contact.toBare()) == whiteboardWindows_.end()) {
+			return NULL;
+		}
+		return whiteboardWindows_[contact.toBare()];
+	}
+
+	void WhiteboardManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
+		boost::shared_ptr<RequestWhiteboardUIEvent> requestWhiteboardEvent = boost::dynamic_pointer_cast<RequestWhiteboardUIEvent>(event);
+		if (requestWhiteboardEvent) {
+			requestSession(requestWhiteboardEvent->getContact());
+		}
+		boost::shared_ptr<AcceptWhiteboardSessionUIEvent> sessionAcceptEvent = boost::dynamic_pointer_cast<AcceptWhiteboardSessionUIEvent>(event);
+		if (sessionAcceptEvent) {
+			acceptSession(sessionAcceptEvent->getContact());
+		}
+		boost::shared_ptr<CancelWhiteboardSessionUIEvent> sessionCancelEvent = boost::dynamic_pointer_cast<CancelWhiteboardSessionUIEvent>(event);
+		if (sessionCancelEvent) {
+			cancelSession(sessionCancelEvent->getContact());
+		}
+		boost::shared_ptr<ShowWhiteboardUIEvent> showWindowEvent = boost::dynamic_pointer_cast<ShowWhiteboardUIEvent>(event);
+		if (showWindowEvent) {
+			WhiteboardWindow* window = findWhiteboardWindow(showWindowEvent->getContact());
+			if (window != NULL) {
+				window->activateWindow();
+			}
+		}
+	}
+
+	void WhiteboardManager::acceptSession(const JID& from) {
+		IncomingWhiteboardSession::ref session = boost::dynamic_pointer_cast<IncomingWhiteboardSession>(whiteboardSessionManager_->getSession(from));
+		WhiteboardWindow* window = findWhiteboardWindow(from);
+		if (session && window) {
+			session->accept();
+			window->show();
+		}
+	}
+
+	void WhiteboardManager::requestSession(const JID& contact) {
+		WhiteboardSession::ref session = whiteboardSessionManager_->requestSession(contact);
+		session->onSessionTerminated.connect(boost::bind(&WhiteboardManager::handleSessionTerminate, this, _1));
+		session->onRequestAccepted.connect(boost::bind(&WhiteboardManager::handleSessionAccept, this, _1));
+		session->onRequestRejected.connect(boost::bind(&WhiteboardManager::handleRequestReject, this, _1));
+
+		WhiteboardWindow* window = findWhiteboardWindow(contact);
+		if (window == NULL) {
+			createNewWhiteboardWindow(contact, session);
+		} else {
+			window->setSession(session);
+		}
+		onSessionRequest(session->getTo(), true);
+	}
+
+	void WhiteboardManager::cancelSession(const JID& from) {
+		WhiteboardSession::ref session = whiteboardSessionManager_->getSession(from);
+		if (session) {
+			session->cancel();
+		}
+	}
+
+	void WhiteboardManager::handleIncomingSession(IncomingWhiteboardSession::ref session) {
+		session->onSessionTerminated.connect(boost::bind(&WhiteboardManager::handleSessionTerminate, this, _1));
+		session->onRequestAccepted.connect(boost::bind(&WhiteboardManager::handleSessionAccept, this, _1));
+
+		WhiteboardWindow* window = findWhiteboardWindow(session->getTo());
+		if (window == NULL) {
+			createNewWhiteboardWindow(session->getTo(), session);
+		} else {
+			window->setSession(session);
+		}
+
+		onSessionRequest(session->getTo(), false);
+	}
+
+	void WhiteboardManager::handleSessionTerminate(const JID& contact) {
+		onSessionTerminate(contact);
+	}
+
+	void WhiteboardManager::handleSessionCancel(const JID& contact) {
+		onSessionTerminate(contact);
+	}
+
+	void WhiteboardManager::handleSessionAccept(const JID& contact) {
+		WhiteboardWindow* window = findWhiteboardWindow(contact);
+		window->show();
+		onRequestAccepted(contact);
+	}
+
+	void WhiteboardManager::handleRequestReject(const JID& contact) {
+		onRequestRejected(contact);
+	}
+
+}
diff --git a/Swift/Controllers/WhiteboardManager.h b/Swift/Controllers/WhiteboardManager.h
new file mode 100644
index 0000000..2f5767b
--- /dev/null
+++ b/Swift/Controllers/WhiteboardManager.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+
+#pragma once
+
+#include <map>
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/JID/JID.h>
+
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/WhiteboardWindow.h>
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Whiteboard/IncomingWhiteboardSession.h>
+
+namespace Swift {
+	class WhiteboardSessionManager;
+	class NickResolver;
+
+	class WhiteboardManager {
+	public:
+		WhiteboardManager(WhiteboardWindowFactory* whiteboardWindowFactory, UIEventStream* uiEventStream, NickResolver* nickResolver, WhiteboardSessionManager* whiteboardSessionManager);
+		~WhiteboardManager();
+
+		WhiteboardWindow* createNewWhiteboardWindow(const JID& contact, WhiteboardSession::ref session);
+
+	public:
+		boost::signal< void (const JID&, bool senderIsSelf)> onSessionRequest;
+		boost::signal< void (const JID&)> onSessionTerminate;
+		boost::signal< void (const JID&)> onRequestAccepted;
+		boost::signal< void (const JID&)> onRequestRejected;
+
+	private:
+		void handleUIEvent(boost::shared_ptr<UIEvent> event);
+		void handleSessionTerminate(const JID& contact);
+		void handleSessionCancel(const JID& contact);
+		void handleSessionAccept(const JID& contact);
+		void handleRequestReject(const JID& contact);
+		void handleIncomingSession(IncomingWhiteboardSession::ref session);
+		void acceptSession(const JID& from);
+		void requestSession(const JID& contact);
+		void cancelSession(const JID& from);
+		WhiteboardWindow* findWhiteboardWindow(const JID& contact);
+
+	private:
+		std::map<JID, WhiteboardWindow*> whiteboardWindows_;
+		WhiteboardWindowFactory* whiteboardWindowFactory_;
+		UIEventStream* uiEventStream_;
+		NickResolver* nickResolver_;
+		boost::bsignals::scoped_connection uiEventConnection_;
+		WhiteboardSessionManager* whiteboardSessionManager_;
+	};
+}
diff --git a/Swift/QtUI/ChatList/ChatListDelegate.cpp b/Swift/QtUI/ChatList/ChatListDelegate.cpp
index bcd1585..5b879df 100644
--- a/Swift/QtUI/ChatList/ChatListDelegate.cpp
+++ b/Swift/QtUI/ChatList/ChatListDelegate.cpp
@@ -12,6 +12,7 @@
 #include "Swift/QtUI/ChatList/ChatListItem.h"
 #include "Swift/QtUI/ChatList/ChatListMUCItem.h"
 #include "Swift/QtUI/ChatList/ChatListRecentItem.h"
+#include "Swift/QtUI/ChatList/ChatListWhiteboardItem.h"
 #include "Swift/QtUI/ChatList/ChatListGroupItem.h"
 
 namespace Swift {
@@ -38,6 +39,9 @@ QSize ChatListDelegate::sizeHint(const QStyleOptionViewItem& option, const QMode
 	}
 	else if (item && dynamic_cast<ChatListGroupItem*>(item)) {
 		return groupDelegate_->sizeHint(option, index);
+	}
+	else if (item && dynamic_cast<ChatListWhiteboardItem*>(item)) {
+		return common_.contactSizeHint(option, index, compact_);
 	} 
 	return QStyledItemDelegate::sizeHint(option, index);
 }
@@ -65,6 +69,9 @@ void ChatListDelegate::paint(QPainter* painter, const QStyleOptionViewItem& opti
 		ChatListGroupItem* group = dynamic_cast<ChatListGroupItem*>(item);
 		groupDelegate_->paint(painter, option, group->data(Qt::DisplayRole).toString(), group->rowCount(), option.state & QStyle::State_Open); 
 	}
+	else if (item && dynamic_cast<ChatListWhiteboardItem*>(item)) {
+		paintWhiteboard(painter, option, dynamic_cast<ChatListWhiteboardItem*>(item));
+	}
 	else {
 		QStyledItemDelegate::paint(painter, option, index);
 	}
@@ -116,4 +123,19 @@ void ChatListDelegate::paintRecent(QPainter* painter, const QStyleOptionViewItem
 	common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, item->getChat().unreadCount, compact_);
 }
 
+void ChatListDelegate::paintWhiteboard(QPainter* painter, const QStyleOptionViewItem& option, ChatListWhiteboardItem* item) const {
+	QColor nameColor = item->data(Qt::TextColorRole).value<QColor>();
+	QString avatarPath;
+	if (item->data(ChatListWhiteboardItem::AvatarRole).isValid() && !item->data(ChatListWhiteboardItem::AvatarRole).value<QString>().isNull()) {
+		avatarPath = item->data(ChatListWhiteboardItem::AvatarRole).value<QString>();
+	}
+	QIcon presenceIcon;/* = item->data(ChatListWhiteboardItem::PresenceIconRole).isValid() && !item->data(ChatListWhiteboardItem::PresenceIconRole).value<QIcon>().isNull()
+			? item->data(ChatListWhiteboardItem::PresenceIconRole).value<QIcon>()
+			: QIcon(":/icons/offline.png");*/
+	QString name = item->data(Qt::DisplayRole).toString();
+	//qDebug() << "Avatar for " << name << " = " << avatarPath;
+	QString statusText = item->data(ChatListWhiteboardItem::DetailTextRole).toString();
+	common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, item->getChat().unreadCount, compact_);
+}
+
 }
diff --git a/Swift/QtUI/ChatList/ChatListDelegate.h b/Swift/QtUI/ChatList/ChatListDelegate.h
index 5ac45ce..9460c28 100644
--- a/Swift/QtUI/ChatList/ChatListDelegate.h
+++ b/Swift/QtUI/ChatList/ChatListDelegate.h
@@ -13,6 +13,7 @@
 namespace Swift {
 	class ChatListMUCItem;
 	class ChatListRecentItem;
+	class ChatListWhiteboardItem;
 	class ChatListDelegate : public QStyledItemDelegate {
 		public:
 			ChatListDelegate(bool compact);
@@ -24,6 +25,7 @@ namespace Swift {
 		private:
 			void paintMUC(QPainter* painter, const QStyleOptionViewItem& option, ChatListMUCItem* item) const;
 			void paintRecent(QPainter* painter, const QStyleOptionViewItem& option, ChatListRecentItem* item) const;
+			void paintWhiteboard(QPainter* painter, const QStyleOptionViewItem& option, ChatListWhiteboardItem* item) const;
 			QSize mucSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const;
 			QSize recentSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const;
 
diff --git a/Swift/QtUI/ChatList/ChatListModel.cpp b/Swift/QtUI/ChatList/ChatListModel.cpp
index 681c1c2..15f4dfe 100644
--- a/Swift/QtUI/ChatList/ChatListModel.cpp
+++ b/Swift/QtUI/ChatList/ChatListModel.cpp
@@ -8,6 +8,7 @@
 
 #include <Swift/QtUI/ChatList/ChatListMUCItem.h>
 #include <Swift/QtUI/ChatList/ChatListRecentItem.h>
+#include <Swift/QtUI/ChatList/ChatListWhiteboardItem.h>
 
 namespace Swift {
 
@@ -15,8 +16,10 @@ ChatListModel::ChatListModel() {
 	root_ = new ChatListGroupItem("", NULL, false);
 	mucBookmarks_ = new ChatListGroupItem(tr("Bookmarked Rooms"), root_);
 	recents_ = new ChatListGroupItem(tr("Recent Chats"), root_, false);
+	whiteboards_ = new ChatListGroupItem(tr("Opened Whiteboards"), root_, false);
 	root_->addItem(recents_);
 	root_->addItem(mucBookmarks_);
+	root_->addItem(whiteboards_);
 }
 
 void ChatListModel::clearBookmarks() {
@@ -46,11 +49,30 @@ void ChatListModel::removeMUCBookmark(const Swift::MUCBookmark& bookmark) {
 	}
 }
 
+void ChatListModel::addWhiteboardSession(const ChatListWindow::Chat& chat) {
+	emit layoutAboutToBeChanged();
+	whiteboards_->addItem(new ChatListWhiteboardItem(chat, whiteboards_));
+	emit layoutChanged();
+}
+
+void ChatListModel::removeWhiteboardSession(const JID& jid) {
+	for (int i = 0; i < whiteboards_->rowCount(); i++) {
+		ChatListWhiteboardItem* item = dynamic_cast<ChatListWhiteboardItem*>(whiteboards_->item(i));
+		if (item->getChat().jid == jid) {
+			emit layoutAboutToBeChanged();
+			whiteboards_->remove(i);
+			emit layoutChanged();
+			break;
+		}
+	}
+}
+
 void ChatListModel::setRecents(const std::list<ChatListWindow::Chat>& recents) {
 	emit layoutAboutToBeChanged();
 	recents_->clear();
 	foreach (const ChatListWindow::Chat chat, recents) {
 		recents_->addItem(new ChatListRecentItem(chat, recents_));
+//whiteboards_->addItem(new ChatListRecentItem(chat, whiteboards_));
 	}
 	emit layoutChanged();
 }
diff --git a/Swift/QtUI/ChatList/ChatListModel.h b/Swift/QtUI/ChatList/ChatListModel.h
index 8e7828c..e384a04 100644
--- a/Swift/QtUI/ChatList/ChatListModel.h
+++ b/Swift/QtUI/ChatList/ChatListModel.h
@@ -23,6 +23,8 @@ namespace Swift {
 			ChatListModel();
 			void addMUCBookmark(const MUCBookmark& bookmark);
 			void removeMUCBookmark(const MUCBookmark& bookmark);
+			void addWhiteboardSession(const ChatListWindow::Chat& chat);
+			void removeWhiteboardSession(const JID& jid);
 			int columnCount(const QModelIndex& parent = QModelIndex()) const;
 			QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
 			QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const;
@@ -34,6 +36,7 @@ namespace Swift {
 		private:
 			ChatListGroupItem* mucBookmarks_;
 			ChatListGroupItem* recents_;
+			ChatListGroupItem* whiteboards_;
 			ChatListGroupItem* root_;
 	};
 
diff --git a/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp b/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp
new file mode 100644
index 0000000..41648b6
--- /dev/null
+++ b/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/QtUI/ChatList/ChatListWhiteboardItem.h>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
+
+namespace Swift {
+	ChatListWhiteboardItem::ChatListWhiteboardItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent) : ChatListItem(parent), chat_(chat) {
+
+	}
+
+	const ChatListWindow::Chat& ChatListWhiteboardItem::getChat() const {
+		return chat_;
+	}
+
+	QVariant ChatListWhiteboardItem::data(int role) const {
+		switch (role) {
+		case Qt::DisplayRole: return P2QSTRING(chat_.chatName);
+		case DetailTextRole: return P2QSTRING(chat_.activity);
+			/*case Qt::TextColorRole: return textColor_;
+			  case Qt::BackgroundColorRole: return backgroundColor_;
+			  case Qt::ToolTipRole: return isContact() ? toolTipString() : QVariant();
+			  case StatusTextRole: return statusText_;*/
+		case AvatarRole: return QVariant(QString(chat_.avatarPath.string().c_str()));
+		case PresenceIconRole: return getPresenceIcon();
+		default: return QVariant();
+		}
+	}
+
+	QIcon ChatListWhiteboardItem::getPresenceIcon() const {
+		QString iconString;
+		switch (chat_.statusType) {
+	 	case StatusShow::Online: iconString = "online";break;
+	 	case StatusShow::Away: iconString = "away";break;
+	 	case StatusShow::XA: iconString = "away";break;
+	 	case StatusShow::FFC: iconString = "online";break;
+	 	case StatusShow::DND: iconString = "dnd";break;
+	 	case StatusShow::None: iconString = "offline";break;
+		}
+		return QIcon(":/icons/" + iconString + ".png");
+	}
+}
+
diff --git a/Swift/QtUI/ChatList/ChatListWhiteboardItem.h b/Swift/QtUI/ChatList/ChatListWhiteboardItem.h
new file mode 100644
index 0000000..2dc6255
--- /dev/null
+++ b/Swift/QtUI/ChatList/ChatListWhiteboardItem.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2012 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QList>
+#include <QIcon>
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/MUC/MUCBookmark.h>
+#include <Swift/Controllers/UIInterfaces/ChatListWindow.h>
+
+#include <Swift/QtUI/ChatList/ChatListItem.h>
+
+namespace Swift {
+	class ChatListWhiteboardItem : public ChatListItem {
+		public:
+			enum RecentItemRoles {
+				DetailTextRole = Qt::UserRole,
+				AvatarRole = Qt::UserRole + 1,
+				PresenceIconRole = Qt::UserRole + 2/*,
+				StatusShowTypeRole = Qt::UserRole + 3*/
+			};
+			ChatListWhiteboardItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent);
+			const ChatListWindow::Chat& getChat() const;
+			QVariant data(int role) const;
+		private:
+			QIcon getPresenceIcon() const;
+			ChatListWindow::Chat chat_;
+	};
+}
diff --git a/Swift/QtUI/ChatList/QtChatListWindow.cpp b/Swift/QtUI/ChatList/QtChatListWindow.cpp
index 42eb43b..9692c9c 100644
--- a/Swift/QtUI/ChatList/QtChatListWindow.cpp
+++ b/Swift/QtUI/ChatList/QtChatListWindow.cpp
@@ -13,6 +13,7 @@
 
 #include <Swift/QtUI/ChatList/ChatListMUCItem.h>
 #include <Swift/QtUI/ChatList/ChatListRecentItem.h>
+#include <Swift/QtUI/ChatList/ChatListWhiteboardItem.h>
 #include <Swift/QtUI/QtAddBookmarkWindow.h>
 #include <Swift/QtUI/QtEditBookmarkWindow.h>
 #include <Swift/QtUI/QtUISettingConstants.h>
@@ -21,6 +22,7 @@
 #include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h>
 #include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h>
 #include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h>
+#include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h>
 #include <Swift/Controllers/Settings/SettingsProvider.h>
 
 
@@ -97,6 +99,11 @@ void QtChatListWindow::handleItemActivated(const QModelIndex& index) {
 			onRecentActivated(recentItem->getChat());
 		}
 	}
+	else if (ChatListWhiteboardItem* whiteboardItem = dynamic_cast<ChatListWhiteboardItem*>(item)) {
+		if (!whiteboardItem->getChat().isMUC || bookmarksEnabled_) {
+			eventStream_->send(boost::make_shared<ShowWhiteboardUIEvent>(whiteboardItem->getChat().jid));
+		}
+	}
 }
 
 void QtChatListWindow::clearBookmarks() {
@@ -111,6 +118,14 @@ void QtChatListWindow::removeMUCBookmark(const MUCBookmark& bookmark) {
 	model_->removeMUCBookmark(bookmark);
 }
 
+void QtChatListWindow::addWhiteboardSession(const ChatListWindow::Chat& chat) {
+	model_->addWhiteboardSession(chat);
+}
+
+void QtChatListWindow::removeWhiteboardSession(const JID& jid) {
+	model_->removeWhiteboardSession(jid);
+}
+
 void QtChatListWindow::setRecents(const std::list<ChatListWindow::Chat>& recents) {
 	model_->setRecents(recents);
 }
diff --git a/Swift/QtUI/ChatList/QtChatListWindow.h b/Swift/QtUI/ChatList/QtChatListWindow.h
index 33131ce..ef4ce0f 100644
--- a/Swift/QtUI/ChatList/QtChatListWindow.h
+++ b/Swift/QtUI/ChatList/QtChatListWindow.h
@@ -22,6 +22,8 @@ namespace Swift {
 			virtual ~QtChatListWindow();
 			void addMUCBookmark(const MUCBookmark& bookmark);
 			void removeMUCBookmark(const MUCBookmark& bookmark);
+			void addWhiteboardSession(const ChatListWindow::Chat& chat);
+			void removeWhiteboardSession(const JID& jid);
 			void setBookmarksEnabled(bool enabled);
 			void setRecents(const std::list<ChatListWindow::Chat>& recents);
 			void setUnreadCount(int unread);
diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp
index eaec3b6..433ade4 100644
--- a/Swift/QtUI/QtChatView.cpp
+++ b/Swift/QtUI/QtChatView.cpp
@@ -430,6 +430,20 @@ void QtChatView::setFileTransferStatus(QString id, const ChatWindow::FileTransfe
 	ftElement.setInnerXml(newInnerHTML);
 }
 
+void QtChatView::setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state) {
+	QWebElement divElement = findDivElementWithID(document_, id);
+	QString newInnerHTML;
+	if (state == ChatWindow::WhiteboardAccepted) {
+		newInnerHTML =	tr("Started whiteboard chat") + "<br/>" +
+			QtChatWindow::buildChatWindowButton(tr("Show whiteboard"), QtChatWindow::ButtonWhiteboardShowWindow, id);
+	} else if (state == ChatWindow::WhiteboardTerminated) {
+		newInnerHTML =	tr("Whiteboard chat has been canceled");
+	} else if (state == ChatWindow::WhiteboardRejected) {
+		newInnerHTML =	tr("Whiteboard chat request has been rejected");
+	}
+	divElement.setInnerXml(newInnerHTML);
+}
+
 void QtChatView::setMUCInvitationJoined(QString id) {
 	QWebElement divElement = findDivElementWithID(document_, id);
 	QWebElement buttonElement = divElement.findFirst("input#mucinvite");
diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h
index 118f14b..9080808 100644
--- a/Swift/QtUI/QtChatView.h
+++ b/Swift/QtUI/QtChatView.h
@@ -44,6 +44,7 @@ namespace Swift {
 			void addToJSEnvironment(const QString&, QObject*);
 			void setFileTransferProgress(QString id, const int percentageDone);
 			void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg);
+			void setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state);
 			void setMUCInvitationJoined(QString id);
 			void showEmoticons(bool show);
 			int getSnippetPositionByDate(const QDate& date);
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index 2bb9ae0..6c96b34 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -54,6 +54,9 @@
 
 namespace Swift {
 
+const QString QtChatWindow::ButtonWhiteboardSessionCancel = QString("whiteboard-cancel");
+const QString QtChatWindow::ButtonWhiteboardSessionAcceptRequest = QString("whiteboard-acceptrequest");
+const QString QtChatWindow::ButtonWhiteboardShowWindow = QString("whiteboard-showwindow");
 const QString QtChatWindow::ButtonFileTransferCancel = QString("filetransfer-cancel");
 const QString QtChatWindow::ButtonFileTransferSetDescription = QString("filetransfer-setdescription");
 const QString QtChatWindow::ButtonFileTransferSendRequest = QString("filetransfer-sendrequest");
@@ -649,6 +652,39 @@ void QtChatWindow::setFileTransferStatus(std::string id, const FileTransferState
 	messageLog_->setFileTransferStatus(QString::fromStdString(id), state, QString::fromStdString(msg));
 }
 
+std::string QtChatWindow::addWhiteboardRequest(bool senderIsSelf) {
+	QString wb_id = QString("wb%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
+	QString htmlString;
+	if (senderIsSelf) {
+		htmlString = "<div id='" + wb_id + "'>" + tr("Starting whiteboard chat") + "<br />"+
+				buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) +
+			"</div>";
+	} else {
+		htmlString = "<div id='" + wb_id + "'>" + Qt::escape(contact_) + tr(" would like to start whiteboard chat") + ": <br/>" +
+				buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) +
+				buildChatWindowButton(tr("Accept"), ButtonWhiteboardSessionAcceptRequest, wb_id) +
+			"</div>";
+	}
+
+	if (lastLineTracker_.getShouldMoveLastLine()) {
+		/* should this be queued? */
+		messageLog_->addLastSeenLine();
+		/* if the line is added we should break the snippet */
+//		appendToPrevious = false;
+	}
+	QString qAvatarPath = "qrc:/icons/avatar.png";
+	std::string id = "wbmessage" + boost::lexical_cast<std::string>(idCounter_++);
+	messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(contact_), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, false, theme_, P2QSTRING(id))));
+
+	previousMessageWasSelf_ = false;
+	previousSenderName_ = contact_;
+	return Q2PSTRING(wb_id);
+}
+
+void QtChatWindow::setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) {
+	messageLog_->setWhiteboardSessionStatus(QString::fromStdString(id), state);
+}
+
 void QtChatWindow::handleHTMLButtonClicked(QString id, QString encodedArgument1, QString encodedArgument2, QString encodedArgument3) {
 	QString arg1 = decodeButtonArgument(encodedArgument1);
 	QString arg2 = decodeButtonArgument(encodedArgument2);
@@ -681,6 +717,20 @@ void QtChatWindow::handleHTMLButtonClicked(QString id, QString encodedArgument1,
 			onFileTransferAccept(Q2PSTRING(ft_id), Q2PSTRING(path));
 		}
 	}
+	else if (id.startsWith(ButtonWhiteboardSessionAcceptRequest)) {
+		QString id = arg1;
+		messageLog_->setWhiteboardSessionStatus(QString::fromStdString(Q2PSTRING(id)), ChatWindow::WhiteboardAccepted);
+		onWhiteboardSessionAccept();
+	}
+	else if (id.startsWith(ButtonWhiteboardSessionCancel)) {
+		QString id = arg1;
+		messageLog_->setWhiteboardSessionStatus(QString::fromStdString(Q2PSTRING(id)), ChatWindow::WhiteboardTerminated);
+		onWhiteboardSessionCancel();
+	}
+	else if (id.startsWith(ButtonWhiteboardShowWindow)) {
+		QString id = arg1;
+		onWhiteboardWindowShow();
+	}
 	else if (id.startsWith(ButtonMUCInvite)) {
 		QString roomJID = arg1;
 		QString password = arg2;
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index a703818..3416b42 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -73,6 +73,9 @@ namespace Swift {
 		Q_OBJECT
 
 		public:
+			static const QString ButtonWhiteboardSessionCancel;
+			static const QString ButtonWhiteboardSessionAcceptRequest;
+			static const QString ButtonWhiteboardShowWindow;
 			static const QString ButtonFileTransferCancel;
 			static const QString ButtonFileTransferSetDescription;
 			static const QString ButtonFileTransferSendRequest;
@@ -94,6 +97,9 @@ namespace Swift {
 			void setFileTransferProgress(std::string id, const int percentageDone);
 			void setFileTransferStatus(std::string id, const FileTransferState state, const std::string& msg);
 			
+			std::string addWhiteboardRequest(bool senderIsSelf);
+			void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state);
+
 			void show();
 			void activate();
 			void setUnreadMessageCount(int count);
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index 2197ec6..a154fb0 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -25,9 +25,11 @@
 #include "QtContactEditWindow.h"
 #include "QtAdHocCommandWindow.h"
 #include "QtFileTransferListWidget.h"
+#include "Whiteboard/QtWhiteboardWindow.h"
 #include <Swift/Controllers/Settings/SettingsProviderHierachy.h>
 #include <Swift/QtUI/QtUISettingConstants.h>
 #include <QtHistoryWindow.h>
+#include <Swiften/Whiteboard/WhiteboardSession.h>
 
 namespace Swift {
 
@@ -155,6 +157,10 @@ ContactEditWindow* QtUIFactory::createContactEditWindow() {
 	return new QtContactEditWindow();
 }
 
+WhiteboardWindow* QtUIFactory::createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession) {
+	return new QtWhiteboardWindow(whiteboardSession);
+}
+
 void QtUIFactory::createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) {
 	new QtAdHocCommandWindow(command);
 }
diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h
index 1b2431f..30f0101 100644
--- a/Swift/QtUI/QtUIFactory.h
+++ b/Swift/QtUI/QtUIFactory.h
@@ -25,6 +25,7 @@ namespace Swift {
 	class QtChatWindow;
 	class TimerFactory;
 	class historyWindow_;
+	class WhiteboardSession;
 
 	class QtUIFactory : public QObject, public UIFactory {
 			Q_OBJECT
@@ -44,6 +45,7 @@ namespace Swift {
 			virtual ProfileWindow* createProfileWindow();
 			virtual ContactEditWindow* createContactEditWindow();
 			virtual FileTransferListWidget* createFileTransferListWidget();
+			virtual WhiteboardWindow* createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession);
 			virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command);
 
 		private slots:
diff --git a/Swift/QtUI/Roster/QtRosterWidget.cpp b/Swift/QtUI/Roster/QtRosterWidget.cpp
index 2fe7f33..1cf073b 100644
--- a/Swift/QtUI/Roster/QtRosterWidget.cpp
+++ b/Swift/QtUI/Roster/QtRosterWidget.cpp
@@ -15,6 +15,7 @@
 #include "Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h"
 #include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h"
 #include "Swift/Controllers/UIEvents/SendFileUIEvent.h"
+#include "Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h"
 #include "QtContactEditWindow.h"
 #include "Swift/Controllers/Roster/ContactRosterItem.h"
 #include "Swift/Controllers/Roster/GroupRosterItem.h"
@@ -62,6 +63,12 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) {
 			sendFile = contextMenu.addAction(tr("Send File"));
 		}
 #endif
+#ifdef SWIFT_EXPERIMENTAL_WB
+		QAction* startWhiteboardChat = NULL;
+		if (contact->supportsFeature(ContactRosterItem::WhiteboardFeature)) {
+			startWhiteboardChat = contextMenu.addAction(tr("Start Whiteboard Chat"));
+		}
+#endif
 		QAction* result = contextMenu.exec(event->globalPos());
 		if (result == editContact) {
 			eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID()));
@@ -79,6 +86,11 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) {
 			}
 		}
 #endif
+#ifdef SWIFT_EXPERIMENTAL_WB
+		else if (startWhiteboardChat && result == startWhiteboardChat) {
+			eventStream_->send(boost::make_shared<RequestWhiteboardUIEvent>(contact->getJID()));
+		}
+#endif
 	}
 	else if (GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item)) {
 		QAction* renameGroupAction = contextMenu.addAction(tr("Rename"));
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index fe186e5..5ab9c9e 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -135,6 +135,7 @@ sources = [
     "ChatList/ChatListDelegate.cpp",
     "ChatList/ChatListMUCItem.cpp",
     "ChatList/ChatListRecentItem.cpp",
+    "ChatList/ChatListWhiteboardItem.cpp",
     "MUCSearch/QtMUCSearchWindow.cpp",
     "MUCSearch/MUCSearchModel.cpp",
     "MUCSearch/MUCSearchRoomItem.cpp",
@@ -147,6 +148,11 @@ sources = [
     "UserSearch/QtUserSearchWindow.cpp",
     "UserSearch/UserSearchModel.cpp",
     "UserSearch/UserSearchDelegate.cpp",
+	"Whiteboard/FreehandLineItem.cpp",
+	"Whiteboard/GView.cpp",
+	"Whiteboard/TextDialog.cpp",
+	"Whiteboard/QtWhiteboardWindow.cpp",
+	"Whiteboard/ColorWidget.cpp",
     "QtSubscriptionRequestWindow.cpp",
     "QtRosterHeader.cpp",
     "QtWebView.cpp",
diff --git a/Swift/QtUI/Swift.qrc b/Swift/QtUI/Swift.qrc
index cf1ee05..eb4f7ee 100644
--- a/Swift/QtUI/Swift.qrc
+++ b/Swift/QtUI/Swift.qrc
@@ -21,5 +21,13 @@
 		<file alias="icons/new-chat.png">../resources/icons/new-chat.png</file>
 		<file alias="icons/actions.png">../resources/icons/actions.png</file>
 		<file alias="COPYING">COPYING</file>
+		<file alias="icons/line.png">../resources/icons/line.png</file>
+		<file alias="icons/rect.png">../resources/icons/rect.png</file>
+		<file alias="icons/circle.png">../resources/icons/circle.png</file>
+		<file alias="icons/handline.png">../resources/icons/handline.png</file>
+		<file alias="icons/text.png">../resources/icons/text.png</file>
+		<file alias="icons/polygon.png">../resources/icons/polygon.png</file>
+		<file alias="icons/cursor.png">../resources/icons/cursor.png</file>
+		<file alias="icons/eraser.png">../resources/icons/eraser.png</file>
 	</qresource>
 </RCC>
diff --git a/Swift/QtUI/Whiteboard/ColorWidget.cpp b/Swift/QtUI/Whiteboard/ColorWidget.cpp
new file mode 100644
index 0000000..e96b760
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/ColorWidget.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+
+#include "ColorWidget.h"
+#include <QPainter>
+#include <QMouseEvent>
+
+namespace Swift {
+	ColorWidget::ColorWidget(QWidget* parent) : QWidget(parent) {
+		setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
+	}
+
+	QSize ColorWidget::sizeHint() const {
+		return QSize(20, 20);
+	}
+
+	void ColorWidget::setColor(QColor color) {
+		this->color = color;
+		update();
+	}
+
+	void ColorWidget::paintEvent(QPaintEvent* /*event*/) {
+		QPainter painter(this);
+		painter.fillRect(0, 0, 20, 20, color);
+	}
+
+	void ColorWidget::mouseReleaseEvent(QMouseEvent* event) {
+		if (event->button() == Qt::LeftButton) {
+			emit clicked();
+		}
+	}
+}
+
diff --git a/Swift/QtUI/Whiteboard/ColorWidget.h b/Swift/QtUI/Whiteboard/ColorWidget.h
new file mode 100644
index 0000000..6abdf00
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/ColorWidget.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QWidget>
+
+namespace Swift {
+	class ColorWidget : public QWidget {
+		Q_OBJECT;
+	public:
+		ColorWidget(QWidget* parent = 0);
+		QSize sizeHint() const;
+
+	public slots:
+		void setColor(QColor color);
+
+	private:
+		QColor color;
+
+	protected:
+		void paintEvent(QPaintEvent* /*event*/);
+		void mouseReleaseEvent(QMouseEvent* event);
+
+	signals:
+		void clicked();
+
+	};
+}
+
diff --git a/Swift/QtUI/Whiteboard/FreehandLineItem.cpp b/Swift/QtUI/Whiteboard/FreehandLineItem.cpp
new file mode 100644
index 0000000..8821062
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/FreehandLineItem.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "FreehandLineItem.h"
+
+
+namespace Swift {
+	FreehandLineItem::FreehandLineItem(QGraphicsItem* parent) : QGraphicsItem(parent) {
+	}
+
+	QRectF FreehandLineItem::boundingRect() const
+	{
+		return boundRect;
+	}
+
+	void FreehandLineItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/)
+	{
+		painter->setPen(pen_);
+		if (points_.size() > 0) {
+			QVector<QPointF>::const_iterator it = points_.begin();
+			QPointF previous = *it;
+			++it;
+			for (; it != points_.end(); ++it) {
+				painter->drawLine(previous, *it);
+				previous = *it;
+			}
+		}
+	}
+
+	void FreehandLineItem::setStartPoint(QPointF point)
+	{
+		points_.clear();
+		points_.append(point);
+		QRectF rect(point, point);
+		prepareGeometryChange();
+		boundRect = rect;
+	}
+
+	void FreehandLineItem::lineTo(QPointF point)
+	{
+		qreal x1, x2, y1, y2;
+		x1 = points_.last().x();
+		x2 = point.x();
+		y1 = points_.last().y();
+		y2 = point.y();
+		if (x1 > x2) {
+			qreal temp = x1;
+			x1 = x2;
+			x2 = temp;
+		}
+		if (y1 > y2) {
+			qreal temp = y1;
+			y1 = y2;
+			y2 = temp;
+		}
+		QRectF rect(x1-1, y1-1, x2+1-x1, y2+1-y1);
+
+		points_.append(point);
+
+		prepareGeometryChange();
+		boundRect |= rect;
+	}
+
+	bool FreehandLineItem::collidesWithPath(const QPainterPath& path, Qt::ItemSelectionMode /*mode*/) const
+	{
+		QVector<QPointF>::const_iterator it;
+		QSizeF size(1,1);
+		for (it = points_.begin(); it != points_.end(); ++it)	{
+			if (path.intersects(QRectF(*it, size))) { 
+				return true;
+			}
+		}
+		return false;
+	}
+
+	void FreehandLineItem::setPen(const QPen& pen)
+	{
+		pen_ = pen;
+		update(boundRect);
+	}
+
+	QPen FreehandLineItem::pen() const
+	{
+		return pen_;
+	}
+
+	const QVector<QPointF>& FreehandLineItem::points() const {
+		return points_;
+	}
+
+	int FreehandLineItem::type() const {
+		return Type;
+	}
+}
diff --git a/Swift/QtUI/Whiteboard/FreehandLineItem.h b/Swift/QtUI/Whiteboard/FreehandLineItem.h
new file mode 100644
index 0000000..f3c6607
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/FreehandLineItem.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QGraphicsItem>
+#include <QPainter>
+#include <iostream>
+
+using namespace std;
+
+namespace Swift {
+	class FreehandLineItem : public QGraphicsItem {
+	public:
+		enum {Type = UserType + 1};
+		FreehandLineItem(QGraphicsItem* parent = 0);
+		QRectF boundingRect() const;
+		void paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/ = 0);
+		void setStartPoint(QPointF point);
+		void lineTo(QPointF point);
+		bool collidesWithPath(const QPainterPath& path, Qt::ItemSelectionMode /*mode*/ = Qt::IntersectsItemShape) const;
+		void setPen(const QPen& pen);
+		QPen pen() const;
+		const QVector<QPointF>& points() const;
+		int type() const;
+
+	private:
+		QPen pen_;
+		QVector<QPointF> points_;
+		QRectF boundRect;
+	};
+}
diff --git a/Swift/QtUI/Whiteboard/GView.cpp b/Swift/QtUI/Whiteboard/GView.cpp
new file mode 100644
index 0000000..d725cbb
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/GView.cpp
@@ -0,0 +1,499 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "GView.h"
+#include <QtSwiftUtil.h>
+
+namespace Swift {
+	GView::GView(QGraphicsScene* scene, QWidget* parent) : QGraphicsView(scene, parent), brush(QColor(Qt::white)), defaultBrush(QColor(Qt::white)) {
+		selectionRect = 0;
+		lastItem = 0;
+		zValue = 0;
+	}
+
+	void GView::setLineWidth(int i) {
+		pen.setWidth(i);
+		if (selectionRect) {
+			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+			changePenAndBrush(selectionRect->data(1).value<QGraphicsItem*>(), pen, brush);
+			lastItemChanged(item, items_.indexOf(item)+1, Update);
+		} else {
+			defaultPen.setWidth(i);
+		}
+	}
+
+	void GView::setLineColor(QColor color) {
+		pen.setColor(color);
+		if (selectionRect) {
+			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+			changePenAndBrush(selectionRect->data(1).value<QGraphicsItem*>(), pen, brush);
+			lastItemChanged(item, items_.indexOf(item)+1, Update);
+		} else {
+			defaultPen.setColor(color);
+		}
+		lineColorChanged(color);
+	}
+
+	QColor GView::getLineColor() {
+		return pen.color();
+	}
+
+	void GView::setBrushColor(QColor color) {
+		brush.setColor(color);
+		if (selectionRect) {
+			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+			changePenAndBrush(selectionRect->data(1).value<QGraphicsItem*>(), pen, brush);
+			lastItemChanged(item, items_.indexOf(item)+1, Update);
+		} else {
+			defaultBrush.setColor(color);
+		}
+		brushColorChanged(color);
+	}
+
+	QColor GView::getBrushColor() {
+		return brush.color();
+	}
+
+	void GView::setMode(Mode mode) {
+		this->mode = mode;
+		lastItem = 0;
+		deselect();
+	}
+
+	void GView::addItem(QGraphicsItem* item, QString id, int pos) {
+		itemsMap_.insert(id, item);
+		if (pos > items_.size()) {
+			item->setZValue(zValue++);
+			scene()->addItem(item);
+			items_.append(item);
+		} else {
+			QGraphicsItem* temp = items_.at(pos-1);
+			item->setZValue(temp->zValue());
+			scene()->addItem(item);
+			item->stackBefore(temp);
+			items_.insert(pos-1, item);
+		}
+	}
+
+	void GView::clear() {
+		scene()->clear();
+		items_.clear();
+		itemsMap_.clear();
+		lastItem = 0;
+		selectionRect = 0;
+		brush = QBrush(QColor(Qt::white));
+		defaultBrush = QBrush(QColor(Qt::white));
+		pen = QPen();
+		pen.setWidth(1);
+		defaultPen = pen;
+		lineWidthChanged(1);
+		lineColorChanged(pen.color());
+		brushColorChanged(brush.color());
+	}
+
+	QGraphicsItem* GView::getItem(QString id) {
+		return itemsMap_.value(id);
+	}
+
+	void GView::deleteItem(QString id) {
+		deselect(id);
+		QGraphicsItem* item = itemsMap_.value(id);
+		items_.removeOne(item);
+		itemsMap_.remove(id);
+		scene()->removeItem(item);
+		delete item;
+	}
+
+	QString GView::getNewID() {
+		return P2QSTRING(idGenerator.generateID());
+	}
+
+	void GView::mouseMoveEvent(QMouseEvent* event)
+	{
+		if (!mousePressed) {
+			return;
+		}
+
+		if (mode == Line) {
+			QGraphicsLineItem* item = qgraphicsitem_cast<QGraphicsLineItem*>(lastItem);
+			if(item != 0) {
+				QLineF line = item->line();
+				line.setP1(this->mapToScene(event->pos()));
+				item->setLine(line);
+
+			}
+		}
+		else if (mode == Rect) {
+			QGraphicsRectItem* item = qgraphicsitem_cast<QGraphicsRectItem*>(lastItem);
+			if (item != 0) {
+				QPointF beginPoint = item->data(0).toPointF();
+				QPointF newPoint = this->mapToScene(event->pos());
+				QRectF rect = item->rect();
+				if (beginPoint.x() <= newPoint.x() && beginPoint.y() <= newPoint.y()) {
+					rect.setTopLeft(beginPoint);
+					rect.setBottomRight(newPoint);
+				}
+				else if (beginPoint.x() > newPoint.x() && beginPoint.y() <= newPoint.y()) {
+					rect.setTopRight(beginPoint);
+					rect.setBottomLeft(newPoint);
+				}
+				else if (beginPoint.x() <= newPoint.x() && beginPoint.y() > newPoint.y()) {
+					rect.setBottomLeft(beginPoint);
+					rect.setTopRight(newPoint);
+				}
+				else if (beginPoint.x() > newPoint.x() && beginPoint.y() > newPoint.y()) {
+					rect.setBottomRight(beginPoint);
+					rect.setTopLeft(newPoint);
+				}
+				item->setRect(rect); 
+			}
+		}
+		else if (mode == Circle) {
+			QGraphicsEllipseItem* item = qgraphicsitem_cast<QGraphicsEllipseItem*>(lastItem);
+			QPainterPath path;
+			QPointF beginPoint = item->data(0).toPointF();
+			QPointF newPoint = this->mapToScene(event->pos());
+			QRectF rect = item->rect();
+			if (beginPoint.x() <= newPoint.x() && beginPoint.y() <= newPoint.y()) {
+				rect.setTopLeft(beginPoint);
+				rect.setBottomRight(newPoint);
+			}
+			else if (beginPoint.x() > newPoint.x() && beginPoint.y() <= newPoint.y()) {
+				rect.setTopRight(beginPoint);
+				rect.setBottomLeft(newPoint);
+			}
+			else if (beginPoint.x() <= newPoint.x() && beginPoint.y() > newPoint.y()) {
+				rect.setBottomLeft(beginPoint);
+				rect.setTopRight(newPoint);
+			}
+			else if (beginPoint.x() > newPoint.x() && beginPoint.y() > newPoint.y()) {
+				rect.setBottomRight(beginPoint);
+				rect.setTopLeft(newPoint);
+			}
+
+			item->setRect(rect);
+		}
+		else if (mode == HandLine) {
+			FreehandLineItem* item  = qgraphicsitem_cast<FreehandLineItem*>(lastItem);
+			if (item != 0) {
+				QPointF newPoint = this->mapToScene(event->pos());
+				item->lineTo(newPoint);
+			}
+		}
+		else if (mode == Polygon) {
+			QGraphicsPolygonItem* item = qgraphicsitem_cast<QGraphicsPolygonItem*>(lastItem);
+			QPointF newPoint = this->mapToScene(event->pos());
+			QPolygonF polygon = item->polygon();
+			polygon.erase(polygon.end()-1);
+			polygon.append(newPoint);
+			item->setPolygon(polygon);
+		}
+		else if (mode == Select) {
+			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+			if (item != 0) {
+				QPainterPath path;
+				QPointF beginPoint = selectionRect->data(0).toPointF();
+				QPointF newPoint = this->mapToScene(event->pos());
+				item->setPos(beginPoint + newPoint);
+				selectionRect->setPos(beginPoint + newPoint);
+			}
+		}
+	}
+
+	void GView::mousePressEvent(QMouseEvent *event)
+	{
+		mousePressed = true;
+		deselect();
+		if (mode == Line) {
+			QPointF point = this->mapToScene(event->pos());
+			QGraphicsItem* item = scene()->addLine(point.x(), point.y(), point.x(), point.y(), pen);
+			QString id = getNewID();
+			item->setZValue(10000000);
+			item->setData(100, id);
+			item->setData(101, items_.size());
+			lastItem = item;
+		}
+		else if (mode == Rect) {
+			QPointF point = this->mapToScene(event->pos());
+			QGraphicsRectItem* item = scene()->addRect(point.x(), point.y(), 0, 0, pen, brush);
+			QString id = getNewID();
+			item->setZValue(10000000);
+			item->setData(0, point);
+			item->setData(100, id);
+			item->setData(101, items_.size());
+			lastItem = item;
+		}
+		else if (mode == Rubber) {
+			QPointF point = this->mapToScene(event->pos());
+			int w = pen.width();
+			QRectF rect(point.x()-w, point.y()-w, w*2, w*2);
+			QList<QGraphicsItem*> list = scene()->items(rect);
+			if (!list.isEmpty())
+			{
+				QGraphicsItem* item = scene()->items(rect).first();
+				QString id = item->data(100).toString();
+				int pos = items_.indexOf(item)+1;
+				itemDeleted(id, pos);
+				deleteItem(id);
+			}
+		}
+		else if (mode == Circle) {
+			QPointF point = this->mapToScene(event->pos());
+			QGraphicsEllipseItem* item = scene()->addEllipse(point.x(), point.y(), 0, 0, pen, brush);
+			QString id = getNewID();
+			item->setZValue(10000000);
+			item->setData(0, point);
+			item->setData(100, id);
+			item->setData(101, items_.size());
+			lastItem = item;
+		}
+		else if (mode == HandLine) {
+			QPointF point = this->mapToScene(event->pos());
+			FreehandLineItem* item = new FreehandLineItem;
+			QString id = getNewID();
+			item->setPen(pen);
+			item->setStartPoint(point);
+			item->setZValue(10000000);
+			item->setData(100, id);
+			item->setData(101, items_.size());
+			scene()->addItem(item);
+			lastItem = item;
+		}
+		else if (mode == Text) {
+			QPointF point = this->mapToScene(event->pos());
+			QGraphicsTextItem* item = scene()->addText("");
+			QString id = getNewID();
+			item->setData(100, id);
+			item->setData(101, items_.size());
+			item->setDefaultTextColor(pen.color());
+			textDialog = new TextDialog(item, this);
+			connect(textDialog, SIGNAL(accepted(QGraphicsTextItem*)), this, SLOT(handleTextItemModified(QGraphicsTextItem*)));
+			textDialog->setAttribute(Qt::WA_DeleteOnClose);
+			textDialog->show();
+			item->setPos(point);
+			lastItem = item;
+		}
+		else if (mode == Polygon) {
+			QPointF point = this->mapToScene(event->pos());
+			QGraphicsPolygonItem* item = dynamic_cast<QGraphicsPolygonItem*>(lastItem);
+			if (item == 0) {
+				QPolygonF polygon;
+				polygon.append(point);
+				polygon.append(point);
+				item = scene()->addPolygon(polygon, pen, brush);
+				QString id = getNewID();
+				item->setZValue(10000000);
+				item->setData(100, id);
+				item->setData(101, items_.size());
+				lastItem = item;
+			}
+			else {
+				QPolygonF polygon;
+				polygon = item->polygon();
+				polygon.append(point);
+				item->setPolygon(polygon);
+			}
+		}
+		else if (mode == Select) {
+			QPointF point = this->mapToScene(event->pos());
+			int w = pen.width();
+			if (w == 0) {
+				w = 1;
+			}
+			QRectF rect(point.x()-w, point.y()-w, w*2, w*2);
+			QList<QGraphicsItem*> list = scene()->items(rect);
+			if (!list.isEmpty()) {
+				QPen pen;
+				pen.setColor(QColor(Qt::gray));
+				pen.setStyle(Qt::DashLine);
+				QGraphicsItem *item = scene()->items(rect).first();
+				selectionRect = scene()->addRect(item->boundingRect(), pen);
+				selectionRect->setZValue(1000000);
+				selectionRect->setData(0, item->pos()-point);
+				selectionRect->setPos(item->pos());
+				QVariant var(QVariant::UserType);
+				var.setValue(item);
+				selectionRect->setData(1, var);
+				setActualPenAndBrushFromItem(item);
+			}
+		}
+	}
+
+	void GView::mouseReleaseEvent(QMouseEvent* /*event*/)
+	{
+		mousePressed = false;
+		QGraphicsPolygonItem* polygon = dynamic_cast<QGraphicsPolygonItem*>(lastItem);
+		if (polygon && polygon->polygon().size() >= 3) {
+			lastItemChanged(polygon, items_.indexOf(polygon)+1, Update);
+		} else if (lastItem) {
+			zValue++;
+			lastItem->setZValue(zValue++);
+			items_.append(lastItem);
+			itemsMap_.insert(lastItem->data(100).toString(), lastItem);
+
+			lastItemChanged(lastItem, items_.size(), New);
+		} else if (selectionRect){
+			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+			lastItemChanged(item, items_.indexOf(item)+1, Update);
+		}
+	}
+
+
+	void GView::handleTextItemModified(QGraphicsTextItem* item) {
+		lastItemChanged(item, item->data(101).toInt(), Update);
+	}
+
+	void GView::moveUpSelectedItem()
+	{
+		if (selectionRect) {
+			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+			int pos = items_.indexOf(item);
+			if (pos < items_.size()-1) {
+				lastItemChanged(item, pos+1, MoveUp);
+				move(item, pos+2);
+			}
+		}
+	}
+
+	void GView::moveDownSelectedItem()
+	{
+		if (selectionRect) {
+			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+			int pos = items_.indexOf(item);
+			if (pos > 0) {
+				lastItemChanged(item, pos+1, MoveDown);
+				move(item, pos);
+			} 
+		}
+	}
+
+	void GView::move(QGraphicsItem* item, int npos) {
+		int pos = items_.indexOf(item);
+		QGraphicsItem* itemAfter = NULL;
+		if (npos-1 > pos) {
+			if (npos == items_.size()) {
+				item->setZValue(zValue++);
+			} else {
+				itemAfter = items_.at(npos);
+			}
+
+			items_.insert(npos, item); 
+			items_.removeAt(pos);
+		} else if (npos-1 < pos) {
+			itemAfter = items_.at(npos-1);
+			items_.insert(npos-1, item);
+			items_.removeAt(pos+1);
+		}
+		if (itemAfter) {
+			item->setZValue(itemAfter->zValue());
+			item->stackBefore(itemAfter);
+		}
+	}
+
+	void GView::changePenAndBrush(QGraphicsItem* item, QPen pen, QBrush brush) {
+		QGraphicsLineItem* lineItem = qgraphicsitem_cast<QGraphicsLineItem*>(item);
+		if (lineItem) {
+			lineItem->setPen(pen);
+		}
+
+		FreehandLineItem* handLineItem = qgraphicsitem_cast<FreehandLineItem*>(item);
+		if (handLineItem) {
+			handLineItem->setPen(pen);
+		}
+
+		QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item);
+		if (rectItem) {
+			rectItem->setPen(pen);
+			rectItem->setBrush(brush);
+		}
+
+		QGraphicsTextItem* textItem = qgraphicsitem_cast<QGraphicsTextItem*>(item);
+		if (textItem) {
+			textItem->setDefaultTextColor(pen.color());
+		}
+
+		QGraphicsPolygonItem* polygonItem = qgraphicsitem_cast<QGraphicsPolygonItem*>(item);
+		if (polygonItem) {
+			polygonItem->setPen(pen);
+			polygonItem->setBrush(brush);
+		}
+
+		QGraphicsEllipseItem* ellipseItem = qgraphicsitem_cast<QGraphicsEllipseItem*>(item);
+		if (ellipseItem) {
+			ellipseItem->setPen(pen);
+			ellipseItem->setBrush(brush);
+		}
+		lineColorChanged(pen.color());
+		brushColorChanged(brush.color());
+	}
+
+	void GView::setActualPenAndBrushFromItem(QGraphicsItem* item) {
+		QGraphicsLineItem* lineItem = qgraphicsitem_cast<QGraphicsLineItem*>(item);
+		if (lineItem) {
+			pen = lineItem->pen();
+		}
+
+		FreehandLineItem* handLineItem = qgraphicsitem_cast<FreehandLineItem*>(item);
+		if (handLineItem) {
+			pen = handLineItem->pen();
+		}
+
+		QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item);
+		if (rectItem) {
+			pen = rectItem->pen();
+			brush = rectItem->brush();
+		}
+
+		QGraphicsTextItem* textItem = qgraphicsitem_cast<QGraphicsTextItem*>(item);
+		if (textItem) {
+			pen.setColor(textItem->defaultTextColor());
+		}
+
+		QGraphicsPolygonItem* polygonItem = qgraphicsitem_cast<QGraphicsPolygonItem*>(item);
+		if (polygonItem) {
+			pen = polygonItem->pen();
+			brush = polygonItem->brush();
+		}
+
+		QGraphicsEllipseItem* ellipseItem = qgraphicsitem_cast<QGraphicsEllipseItem*>(item);
+		if (ellipseItem) {
+			pen = ellipseItem->pen();
+			brush = ellipseItem->brush();
+		}
+		lineWidthChanged(pen.width());
+		lineColorChanged(pen.color());
+		brushColorChanged(brush.color());
+	}
+
+	void GView::deselect() {
+		if (selectionRect != 0)	{
+			pen = defaultPen;
+			brush = defaultBrush;
+			scene()->removeItem(selectionRect);
+			delete selectionRect;
+			selectionRect = 0;
+			lineWidthChanged(pen.width());
+			lineColorChanged(pen.color());
+			brushColorChanged(brush.color());
+		}
+	}
+
+	void GView::deselect(QString id) {
+		if (selectionRect != 0)	{
+			QGraphicsItem* item = getItem(id);
+			if (item && selectionRect->data(1).value<QGraphicsItem*>() == item) {
+				pen = defaultPen;
+				brush = defaultBrush;
+				scene()->removeItem(selectionRect);
+				delete selectionRect;
+				selectionRect = 0;
+				lineWidthChanged(pen.width());
+				lineColorChanged(pen.color());
+				brushColorChanged(brush.color());
+			}
+		}
+	}
+}
diff --git a/Swift/QtUI/Whiteboard/GView.h b/Swift/QtUI/Whiteboard/GView.h
new file mode 100644
index 0000000..88ea326
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/GView.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QGraphicsView>
+#include <QGraphicsLineItem>
+#include <QMouseEvent>
+#include <QPen>
+#include <iostream>
+#include <Swiften/Base/IDGenerator.h>
+
+#include "TextDialog.h"
+#include "FreehandLineItem.h"
+
+namespace Swift {
+	class GView : public QGraphicsView {
+		Q_OBJECT;
+	public:
+		enum Mode {	Rubber, Line, Rect, Circle, HandLine, Text, Polygon, Select };
+		enum Type { New, Update, MoveUp, MoveDown };
+		GView(QGraphicsScene* scene, QWidget* parent = 0);
+		void setLineWidth(int i);
+		void setLineColor(QColor color);
+		QColor getLineColor();
+		void setBrushColor(QColor color);
+		QColor getBrushColor();
+		void setMode(Mode mode);
+		void mouseMoveEvent(QMouseEvent* event);
+		void mousePressEvent(QMouseEvent* event);
+		void mouseReleaseEvent(QMouseEvent* /*event*/);
+		void addItem(QGraphicsItem* item, QString id, int pos);
+		void clear();
+		QGraphicsItem* getItem(QString id);
+		void deleteItem(QString id);
+		QString getNewID();
+		void move(QGraphicsItem* item, int npos);
+		void deselect(QString id);
+
+	public slots:
+		void moveUpSelectedItem();
+		void moveDownSelectedItem();
+
+	private slots:
+		void handleTextItemModified(QGraphicsTextItem*);
+	private:
+		void changePenAndBrush(QGraphicsItem* item, QPen pen, QBrush brush);
+		void setActualPenAndBrushFromItem(QGraphicsItem* item);
+		void deselect();
+
+		int zValue;
+		bool mousePressed;
+		QPen pen;
+		QBrush brush;
+		QPen defaultPen;
+		QBrush defaultBrush;
+		Mode mode;
+		QGraphicsItem* lastItem;
+		QGraphicsRectItem* selectionRect;
+		TextDialog* textDialog;
+		QMap<QString, QGraphicsItem*> itemsMap_;
+		QList<QGraphicsItem*> items_;
+		IDGenerator idGenerator;
+
+	signals:
+		void lastItemChanged(QGraphicsItem* item, int pos, GView::Type type);
+		void itemDeleted(QString id, int pos);
+		void lineWidthChanged(int i);
+		void lineColorChanged(QColor color);
+		void brushColorChanged(QColor color);
+	};
+}
diff --git a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp
new file mode 100644
index 0000000..50d7f54
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "QtWhiteboardWindow.h"
+
+#include <iostream>
+
+#include <boost/bind.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Elements/WhiteboardPayload.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardLineElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardRectElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h>
+#include <Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h>
+
+#include <QMessageBox>
+#include <QLabel>
+
+namespace Swift {
+	QtWhiteboardWindow::QtWhiteboardWindow(WhiteboardSession::ref whiteboardSession) : QWidget() {
+#ifndef Q_WS_MAC
+		setWindowIcon(QIcon(":/logo-icon-16.png"));
+#endif
+		layout = new QVBoxLayout(this);
+		hLayout = new QHBoxLayout;
+		sidebarLayout = new QVBoxLayout;
+		toolboxLayout = new QGridLayout;
+
+		scene = new QGraphicsScene(this);
+		scene->setSceneRect(0, 0, 400, 400);
+
+		graphicsView = new GView(scene, this);
+		graphicsView->setMode(GView::Line);
+		connect(graphicsView, SIGNAL(lastItemChanged(QGraphicsItem*, int, GView::Type)), this, SLOT(handleLastItemChanged(QGraphicsItem*, int, GView::Type)));
+		connect(graphicsView, SIGNAL(itemDeleted(QString, int)), this, SLOT(handleItemDeleted(QString, int)));
+
+		widthBox = new QSpinBox(this);
+		connect(widthBox, SIGNAL(valueChanged(int)), this, SLOT(changeLineWidth(int)));
+		connect(graphicsView, SIGNAL(lineWidthChanged(int)), widthBox, SLOT(setValue(int)));
+		widthBox->setValue(1);
+
+		moveUpButton = new QPushButton("Move Up", this);
+		connect(moveUpButton, SIGNAL(clicked()), graphicsView, SLOT(moveUpSelectedItem()));
+
+		moveDownButton = new QPushButton("Move Down", this);
+		connect(moveDownButton, SIGNAL(clicked()), graphicsView, SLOT(moveDownSelectedItem()));
+
+		strokeLayout = new QHBoxLayout;
+		strokeColor = new ColorWidget;
+		strokeLayout->addWidget(new QLabel("Stroke:"));
+		strokeLayout->addWidget(strokeColor);
+		connect(strokeColor, SIGNAL(clicked()), this, SLOT(showColorDialog()));
+		connect(graphicsView, SIGNAL(lineColorChanged(QColor)), strokeColor, SLOT(setColor(QColor)));
+
+		fillLayout = new QHBoxLayout;
+		fillColor = new ColorWidget;
+		fillLayout->addWidget(new QLabel("Fill:"));
+		fillLayout->addWidget(fillColor);
+		connect(fillColor, SIGNAL(clicked()), this, SLOT(showBrushColorDialog()));
+		connect(graphicsView, SIGNAL(brushColorChanged(QColor)), fillColor, SLOT(setColor(QColor)));
+
+		rubberButton = new QToolButton(this);
+		rubberButton->setIcon(QIcon(":/icons/eraser.png"));
+		rubberButton->setCheckable(true);
+		rubberButton->setAutoExclusive(true);
+		connect(rubberButton, SIGNAL(clicked()), this, SLOT(setRubberMode()));
+
+		lineButton = new QToolButton(this);
+		lineButton->setIcon(QIcon(":/icons/line.png"));
+		lineButton->setCheckable(true);
+		lineButton->setAutoExclusive(true);
+		lineButton->setChecked(true);
+		connect(lineButton, SIGNAL(clicked()), this, SLOT(setLineMode())); 
+
+		rectButton = new QToolButton(this);
+		rectButton->setIcon(QIcon(":/icons/rect.png"));
+		rectButton->setCheckable(true);
+		rectButton->setAutoExclusive(true);
+		connect(rectButton, SIGNAL(clicked()), this, SLOT(setRectMode())); 
+
+		circleButton = new QToolButton(this);
+		circleButton->setIcon(QIcon(":/icons/circle.png"));
+		circleButton->setCheckable(true);
+		circleButton->setAutoExclusive(true);
+		connect(circleButton, SIGNAL(clicked()), this, SLOT(setCircleMode())); 
+
+		handLineButton = new QToolButton(this);
+		handLineButton->setIcon(QIcon(":/icons/handline.png"));
+		handLineButton->setCheckable(true);
+		handLineButton->setAutoExclusive(true);
+		connect(handLineButton, SIGNAL(clicked()), this, SLOT(setHandLineMode())); 
+
+		textButton = new QToolButton(this);
+		textButton->setIcon(QIcon(":/icons/text.png"));
+		textButton->setCheckable(true);
+		textButton->setAutoExclusive(true);
+		connect(textButton, SIGNAL(clicked()), this, SLOT(setTextMode())); 
+
+		polygonButton = new QToolButton(this);
+		polygonButton->setIcon(QIcon(":/icons/polygon.png"));
+		polygonButton->setCheckable(true);
+		polygonButton->setAutoExclusive(true);
+		connect(polygonButton, SIGNAL(clicked()), this, SLOT(setPolygonMode()));
+
+		selectButton = new QToolButton(this);
+		selectButton->setIcon(QIcon(":/icons/cursor.png"));
+		selectButton->setCheckable(true);
+		selectButton->setAutoExclusive(true);
+		connect(selectButton, SIGNAL(clicked()), this, SLOT(setSelectMode()));
+
+		toolboxLayout->addWidget(rubberButton, 0, 0);
+		toolboxLayout->addWidget(selectButton, 0, 1);
+		toolboxLayout->addWidget(lineButton, 0, 2);
+		toolboxLayout->addWidget(circleButton, 1, 0);
+		toolboxLayout->addWidget(handLineButton, 1, 1);
+		toolboxLayout->addWidget(rectButton, 1, 2);
+		toolboxLayout->addWidget(textButton, 2, 0);
+		toolboxLayout->addWidget(polygonButton, 2, 1);
+
+		sidebarLayout->addLayout(toolboxLayout);
+		sidebarLayout->addSpacing(30);
+		sidebarLayout->addWidget(moveUpButton);
+		sidebarLayout->addWidget(moveDownButton);
+		sidebarLayout->addSpacing(40);
+		sidebarLayout->addWidget(widthBox);
+		sidebarLayout->addLayout(strokeLayout);
+		sidebarLayout->addLayout(fillLayout);
+		sidebarLayout->addStretch();
+		hLayout->addWidget(graphicsView);
+		hLayout->addLayout(sidebarLayout);
+		layout->addLayout(hLayout);
+		this->setLayout(layout);
+
+		setSession(whiteboardSession);
+	}
+
+	void QtWhiteboardWindow::handleWhiteboardOperationReceive(const WhiteboardOperation::ref operation) {
+		WhiteboardInsertOperation::ref insertOp = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(operation);
+		if (insertOp) {
+			WhiteboardElementDrawingVisitor visitor(graphicsView, operation->getPos(), GView::New);
+			insertOp->getElement()->accept(visitor);
+		}
+
+		WhiteboardUpdateOperation::ref updateOp = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(operation);
+		if (updateOp) {
+			WhiteboardElementDrawingVisitor visitor(graphicsView, operation->getPos(), GView::Update);
+			updateOp->getElement()->accept(visitor);
+			if (updateOp->getPos() != updateOp->getNewPos()) {
+				graphicsView->move(graphicsView->getItem(P2QSTRING(updateOp->getElement()->getID())), updateOp->getNewPos());
+			}
+		}
+
+		WhiteboardDeleteOperation::ref deleteOp = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(operation);
+		if (deleteOp) {
+			graphicsView->deleteItem(P2QSTRING(deleteOp->getElementID()));
+		}
+	}
+
+	void QtWhiteboardWindow::changeLineWidth(int i)
+	{
+		graphicsView->setLineWidth(i);
+	}
+
+	void QtWhiteboardWindow::showColorDialog()
+	{
+		QColor color = QColorDialog::getColor(graphicsView->getLineColor(), 0, "Select pen color", QColorDialog::ShowAlphaChannel);
+		if(color.isValid())
+			graphicsView->setLineColor(color);
+	}
+
+	void QtWhiteboardWindow::showBrushColorDialog()
+	{
+		QColor color = QColorDialog::getColor(graphicsView->getBrushColor(), 0, "Select brush color", QColorDialog::ShowAlphaChannel);
+		if(color.isValid())
+			graphicsView->setBrushColor(color);
+	}
+
+	void QtWhiteboardWindow::setRubberMode()
+	{
+		graphicsView->setMode(GView::Rubber);
+	}
+
+	void QtWhiteboardWindow::setLineMode()
+	{
+		graphicsView->setMode(GView::Line);
+	}
+
+	void QtWhiteboardWindow::setRectMode()
+	{
+		graphicsView->setMode(GView::Rect);
+	}
+
+	void QtWhiteboardWindow::setCircleMode()
+	{
+		graphicsView->setMode(GView::Circle);
+	}
+
+	void QtWhiteboardWindow::setHandLineMode()
+	{
+		graphicsView->setMode(GView::HandLine);
+	}
+
+	void QtWhiteboardWindow::setTextMode()
+	{
+		graphicsView->setMode(GView::Text);
+	}
+
+	void QtWhiteboardWindow::setPolygonMode()
+	{
+		graphicsView->setMode(GView::Polygon);
+	}
+
+	void QtWhiteboardWindow::setSelectMode()
+	{
+		graphicsView->setMode(GView::Select);
+	}
+
+	void QtWhiteboardWindow::show()
+	{
+		QWidget::show();
+	}
+
+	void QtWhiteboardWindow::setSession(WhiteboardSession::ref session) {
+		graphicsView->clear();
+		whiteboardSession_ = session;
+		whiteboardSession_->onOperationReceived.connect(boost::bind(&QtWhiteboardWindow::handleWhiteboardOperationReceive, this, _1));
+		whiteboardSession_->onRequestAccepted.connect(boost::bind(&QWidget::show, this));
+		whiteboardSession_->onSessionTerminated.connect(boost::bind(&QtWhiteboardWindow::handleSessionTerminate, this));
+	}
+
+	void QtWhiteboardWindow::activateWindow() {
+		QWidget::activateWindow();
+	}
+
+	void QtWhiteboardWindow::setName(const std::string& name) {
+		setWindowTitle(P2QSTRING(name));
+	}
+
+	void QtWhiteboardWindow::handleLastItemChanged(QGraphicsItem* item, int pos, GView::Type type) {
+		WhiteboardElement::ref el;
+		QGraphicsLineItem* lineItem = qgraphicsitem_cast<QGraphicsLineItem*>(item);
+		if (lineItem != 0) {
+			QLine line = lineItem->line().toLine();
+			QColor color = lineItem->pen().color();
+			WhiteboardLineElement::ref element = boost::make_shared<WhiteboardLineElement>(line.x1()+lineItem->pos().x(), line.y1()+lineItem->pos().y(), line.x2()+lineItem->pos().x(), line.y2()+lineItem->pos().y());
+			element->setColor(WhiteboardColor(color.red(), color.green(), color.blue(), color.alpha()));
+			element->setPenWidth(lineItem->pen().width());
+
+			element->setID(lineItem->data(100).toString().toStdString());
+			el = element;
+		}
+
+		FreehandLineItem* freehandLineItem = qgraphicsitem_cast<FreehandLineItem*>(item);
+		if (freehandLineItem != 0) {
+			WhiteboardFreehandPathElement::ref element = boost::make_shared<WhiteboardFreehandPathElement>();
+			QColor color = freehandLineItem->pen().color();
+			std::vector<std::pair<int, int> > points;
+			QVector<QPointF>::const_iterator it = freehandLineItem->points().constBegin();
+			for ( ; it != freehandLineItem->points().constEnd(); ++it) {
+				points.push_back(std::pair<int, int>(it->x()+item->pos().x(), it->y()+item->pos().y()));
+			}
+
+			element->setColor(WhiteboardColor(color.red(), color.green(), color.blue(), color.alpha()));
+			element->setPenWidth(freehandLineItem->pen().width());
+			element->setPoints(points);
+
+			element->setID(freehandLineItem->data(100).toString().toStdString());
+			el = element;
+		}
+
+		QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item);
+		if (rectItem != 0) {
+			QRectF rect = rectItem->rect();
+			WhiteboardRectElement::ref element = boost::make_shared<WhiteboardRectElement>(rect.x()+item->pos().x(), rect.y()+item->pos().y(), rect.width(), rect.height());
+			QColor penColor = rectItem->pen().color();
+			QColor brushColor = rectItem->brush().color();
+
+			element->setBrushColor(WhiteboardColor(brushColor.red(), brushColor.green(), brushColor.blue(), brushColor.alpha()));
+			element->setPenColor(WhiteboardColor(penColor.red(), penColor.green(), penColor.blue(), penColor.alpha()));
+			element->setPenWidth(rectItem->pen().width());
+
+			element->setID(rectItem->data(100).toString().toStdString());
+			el = element;
+		}
+
+		QGraphicsTextItem* textItem = qgraphicsitem_cast<QGraphicsTextItem*>(item);
+		if (textItem != 0) {
+			QPointF point = textItem->pos();
+			WhiteboardTextElement::ref element = boost::make_shared<WhiteboardTextElement>(point.x(), point.y());
+			element->setText(textItem->toPlainText().toStdString());
+			element->setSize(textItem->font().pointSize());
+			QColor color = textItem->defaultTextColor();
+			element->setColor(WhiteboardColor(color.red(), color.green(), color.blue(), color.alpha()));
+
+			element->setID(textItem->data(100).toString().toStdString());
+			el = element;
+		}
+
+		QGraphicsPolygonItem* polygonItem = qgraphicsitem_cast<QGraphicsPolygonItem*>(item);
+		if (polygonItem) {
+			WhiteboardPolygonElement::ref element = boost::make_shared<WhiteboardPolygonElement>();
+			QPolygonF polygon = polygonItem->polygon();
+			std::vector<std::pair<int, int> > points;
+			QVector<QPointF>::const_iterator it = polygon.begin();
+			for (; it != polygon.end(); ++it) {
+				points.push_back(std::pair<int, int>(it->x()+item->pos().x(), it->y()+item->pos().y()));
+			}
+
+			element->setPoints(points);
+
+			QColor penColor = polygonItem->pen().color();
+			QColor brushColor = polygonItem->brush().color();
+			element->setPenColor(WhiteboardColor(penColor.red(), penColor.green(), penColor.blue(), penColor.alpha()));
+			element->setBrushColor(WhiteboardColor(brushColor.red(), brushColor.green(), brushColor.blue(), brushColor.alpha()));
+			element->setPenWidth(polygonItem->pen().width());
+
+			element->setID(polygonItem->data(100).toString().toStdString());
+			el = element;
+		}
+
+		QGraphicsEllipseItem* ellipseItem = qgraphicsitem_cast<QGraphicsEllipseItem*>(item);
+		if (ellipseItem) {
+			QRectF rect = ellipseItem->rect();
+			int cx = rect.x()+rect.width()/2 + item->pos().x();
+			int cy = rect.y()+rect.height()/2 + item->pos().y();
+			int rx = rect.width()/2;
+			int ry = rect.height()/2;
+			WhiteboardEllipseElement::ref element = boost::make_shared<WhiteboardEllipseElement>(cx, cy, rx, ry);
+
+			QColor penColor = ellipseItem->pen().color();
+			QColor brushColor = ellipseItem->brush().color();
+			element->setPenColor(WhiteboardColor(penColor.red(), penColor.green(), penColor.blue(), penColor.alpha()));
+			element->setBrushColor(WhiteboardColor(brushColor.red(), brushColor.green(), brushColor.blue(), brushColor.alpha()));
+			element->setPenWidth(ellipseItem->pen().width());
+
+			element->setID(ellipseItem->data(100).toString().toStdString());
+			el = element;
+		}
+
+		if (type == GView::New) {
+			WhiteboardInsertOperation::ref insertOp = boost::make_shared<WhiteboardInsertOperation>();
+			insertOp->setPos(pos);
+			insertOp->setElement(el);
+			whiteboardSession_->sendOperation(insertOp);
+		} else {
+			WhiteboardUpdateOperation::ref updateOp = boost::make_shared<WhiteboardUpdateOperation>();
+			updateOp->setPos(pos);
+			if (type == GView::Update) {
+				updateOp->setNewPos(pos);
+			} else if (type == GView::MoveUp) {
+				updateOp->setNewPos(pos+1);
+			} else if (type == GView::MoveDown) {
+				updateOp->setNewPos(pos-1);
+			}
+			updateOp->setElement(el);
+			whiteboardSession_->sendOperation(updateOp);
+		}
+	}
+
+	void QtWhiteboardWindow::handleItemDeleted(QString id, int pos) {
+		WhiteboardDeleteOperation::ref deleteOp = boost::make_shared<WhiteboardDeleteOperation>();
+		deleteOp->setElementID(Q2PSTRING(id));
+		deleteOp->setPos(pos);
+		whiteboardSession_->sendOperation(deleteOp);
+	}
+
+	void QtWhiteboardWindow::handleSessionTerminate() {
+		hide();
+	}
+
+	void QtWhiteboardWindow::closeEvent(QCloseEvent* event) {
+		QMessageBox box(this);
+		box.setText(tr("Closing window is equivalent closing the session. Are you sure you want to do this?"));
+		box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+		box.setIcon(QMessageBox::Question);
+		if (box.exec() == QMessageBox::Yes) {
+			whiteboardSession_->cancel();
+		} else {
+			event->ignore();
+		}
+	}
+}
diff --git a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h
new file mode 100644
index 0000000..4665ef0
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIInterfaces/WhiteboardWindow.h>
+#include <Swiften/Elements/Message.h>
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+
+#include <QWidget>
+#include <QGraphicsView>
+#include <QGraphicsScene>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QGridLayout>
+#include <QPainter>
+#include <QPushButton>
+#include <QSpinBox>
+#include <QColorDialog>
+#include <QToolButton>
+#include <QCloseEvent>
+
+#include "GView.h"
+#include "ColorWidget.h"
+
+namespace Swift {
+	class QtWhiteboardWindow : public QWidget, public WhiteboardWindow
+	{
+		Q_OBJECT;
+	public:
+		QtWhiteboardWindow(WhiteboardSession::ref whiteboardSession);
+		void show();
+		void setSession(WhiteboardSession::ref session);
+		void activateWindow();
+		void setName(const std::string& name);
+
+	private slots:
+		void changeLineWidth(int i);
+		void showColorDialog();
+		void showBrushColorDialog();
+		void setRubberMode();
+		void setLineMode();
+		void setRectMode();
+		void setCircleMode();
+		void setHandLineMode();
+		void setTextMode();
+		void setPolygonMode();
+		void setSelectMode();
+		void handleLastItemChanged(QGraphicsItem* item, int pos, GView::Type type);
+		void handleItemDeleted(QString id, int pos);
+
+	private:
+		void handleSessionTerminate();
+		void handleWhiteboardOperationReceive(const WhiteboardOperation::ref operation);
+		void closeEvent(QCloseEvent* event);
+
+	private:
+		QGraphicsScene* scene;
+		GView* graphicsView;
+		QVBoxLayout* layout;
+		QVBoxLayout* sidebarLayout;
+		QHBoxLayout* hLayout;
+		QGridLayout* toolboxLayout;
+		QHBoxLayout* strokeLayout;
+		QHBoxLayout* fillLayout;
+		ColorWidget* strokeColor;
+		ColorWidget* fillColor;
+		QWidget* widget;
+		QPushButton* moveUpButton;
+		QPushButton* moveDownButton;
+		QSpinBox* widthBox;
+		QToolButton* rubberButton;
+		QToolButton* lineButton;
+		QToolButton* rectButton;
+		QToolButton* circleButton;
+		QToolButton* handLineButton;
+		QToolButton* textButton;
+		QToolButton* polygonButton;
+		QToolButton* selectButton;
+
+		std::string lastOpID;
+		WhiteboardSession::ref whiteboardSession_;
+	};
+}
diff --git a/Swift/QtUI/Whiteboard/TextDialog.cpp b/Swift/QtUI/Whiteboard/TextDialog.cpp
new file mode 100644
index 0000000..021895a
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/TextDialog.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "TextDialog.h"
+
+namespace Swift {
+	TextDialog::TextDialog(QGraphicsTextItem* item, QWidget* parent) : QDialog(parent)
+	{
+		this->item = item;
+
+		layout = new QVBoxLayout(this);
+		hLayout = new QHBoxLayout;
+
+		editor = new QLineEdit(this);
+		connect(editor, SIGNAL(textChanged(const QString&)), this, SLOT(changeItemText(const QString&)));
+
+		fontSizeBox = new QSpinBox(this);
+		fontSizeBox->setMinimum(1);
+		connect(fontSizeBox, SIGNAL(valueChanged(int)), this, SLOT(changeItemFontSize(int)));
+		fontSizeBox->setValue(13);
+
+
+		buttonBox = new QDialogButtonBox(this);
+		buttonBox->setStandardButtons(QDialogButtonBox::Ok);
+		connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
+
+		hLayout->addWidget(editor);
+		hLayout->addWidget(fontSizeBox);
+		layout->addLayout(hLayout);
+		layout->addWidget(buttonBox);
+	}
+
+	void TextDialog::changeItemText(const QString &text)
+	{
+		item->setPlainText(text);
+	}
+
+	void TextDialog::changeItemFontSize(int i)
+	{
+		QFont font = item->font();
+		font.setPointSize(i);
+		item->setFont(font);
+	}
+
+	void TextDialog::accept() {
+		emit accepted(item);
+		done(QDialog::Accepted);
+	}
+}
diff --git a/Swift/QtUI/Whiteboard/TextDialog.h b/Swift/QtUI/Whiteboard/TextDialog.h
new file mode 100644
index 0000000..f4d9a13
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/TextDialog.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QDialog>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QDialogButtonBox>
+#include <QLineEdit>
+#include <QGraphicsTextItem>
+#include <QSpinBox>
+
+#include <iostream>
+
+using namespace std;
+
+namespace Swift {
+	class TextDialog : public QDialog
+	{
+		Q_OBJECT;
+	public:
+		TextDialog(QGraphicsTextItem* item, QWidget* parent = 0);
+
+	private:
+		QGraphicsTextItem* item;
+		QLineEdit* editor;
+		QDialogButtonBox* buttonBox;
+		QVBoxLayout* layout;
+		QHBoxLayout* hLayout;
+		QSpinBox* fontSizeBox;
+
+	signals:
+		void accepted(QGraphicsTextItem* item);
+
+	private slots:
+		void accept();
+		void changeItemText(const QString &text);
+		void changeItemFontSize(int i);
+	};
+}
+
diff --git a/Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h b/Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h
new file mode 100644
index 0000000..74c6c1d
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardLineElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardTextElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h>
+#include <Swift/QtUI/Whiteboard/GView.h>
+#include <QtSwiftUtil.h>
+
+namespace Swift {
+	class WhiteboardElementDrawingVisitor : public WhiteboardElementVisitor {
+	public:
+		WhiteboardElementDrawingVisitor(GView* graphicsView, int pos, GView::Type type) : graphicsView_(graphicsView), pos_(pos), type_(type) {}
+
+		void visit(WhiteboardLineElement& element) {
+			QGraphicsLineItem *item;
+			if (type_ == GView::New) {
+				item = new QGraphicsLineItem(element.x1(), element.y1(), element.x2(), element.y2());
+				graphicsView_->addItem(item, P2QSTRING(element.getID()), pos_);
+			} else {
+				item = qgraphicsitem_cast<QGraphicsLineItem*>(graphicsView_->getItem(P2QSTRING(element.getID())));
+				QLineF line(element.x1(), element.y1(), element.x2(), element.y2());
+				item->setLine(line);
+				item->setPos(0,0);
+				graphicsView_->deselect(P2QSTRING(element.getID()));
+			}
+			if (item) {
+				QPen pen;
+				WhiteboardColor color = element.getColor();
+				pen.setColor(QColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()));
+				pen.setWidth(element.getPenWidth());
+				item->setPen(pen);
+				QString id = P2QSTRING(element.getID());
+				item->setData(100, id);
+			}
+		}
+
+		void visit(WhiteboardFreehandPathElement& element) {
+			FreehandLineItem *item;
+			if (type_ == GView::New) {
+				item = new FreehandLineItem;
+			} else {
+				item = qgraphicsitem_cast<FreehandLineItem*>(graphicsView_->getItem(P2QSTRING(element.getID())));
+				item->setPos(0,0);
+				graphicsView_->deselect(P2QSTRING(element.getID()));
+			}
+
+			if (item) {
+				QPen pen;
+				WhiteboardColor color = element.getColor();
+				pen.setColor(QColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()));
+				pen.setWidth(element.getPenWidth());
+				item->setPen(pen);
+
+				std::vector<std::pair<int, int> >::const_iterator it = element.getPoints().begin();
+				item->setStartPoint(QPointF(it->first, it->second));
+				for (++it; it != element.getPoints().end(); ++it) {
+					item->lineTo(QPointF(it->first, it->second));
+				}
+
+				QString id = P2QSTRING(element.getID());
+				item->setData(100, id);
+			}
+			if (type_ == GView::New) {
+				graphicsView_->addItem(item, P2QSTRING(element.getID()), pos_);
+			}
+		}
+
+		void visit(WhiteboardRectElement& element) {
+			QGraphicsRectItem* item;
+			if (type_ == GView::New) {
+				item = new QGraphicsRectItem(element.getX(), element.getY(), element.getWidth(), element.getHeight());
+				graphicsView_->addItem(item, P2QSTRING(element.getID()), pos_);
+			} else {
+				item = qgraphicsitem_cast<QGraphicsRectItem*>(graphicsView_->getItem(P2QSTRING(element.getID())));
+				QRectF rect(element.getX(), element.getY(), element.getWidth(), element.getHeight());
+				item->setRect(rect);
+				item->setPos(0,0);
+				graphicsView_->deselect(P2QSTRING(element.getID()));
+			}
+
+			if (item) {
+				QPen pen;
+				QBrush brush(Qt::SolidPattern);
+				WhiteboardColor penColor = element.getPenColor();
+				WhiteboardColor brushColor = element.getBrushColor();
+				pen.setColor(QColor(penColor.getRed(), penColor.getGreen(), penColor.getBlue(), penColor.getAlpha()));
+				pen.setWidth(element.getPenWidth());
+				brush.setColor(QColor(brushColor.getRed(), brushColor.getGreen(), brushColor.getBlue(), brushColor.getAlpha()));
+				item->setPen(pen);
+				item->setBrush(brush);
+				QString id = P2QSTRING(element.getID());
+				item->setData(100, id);
+			}
+		}
+
+		void visit(WhiteboardPolygonElement& element) {
+			QGraphicsPolygonItem* item = qgraphicsitem_cast<QGraphicsPolygonItem*>(graphicsView_->getItem(P2QSTRING(element.getID())));
+			if (item == 0 && type_ == GView::New) {
+				item = new QGraphicsPolygonItem();
+				QString id = P2QSTRING(element.getID());
+				item->setData(100, id);
+				graphicsView_->addItem(item, id, pos_);
+			}
+			graphicsView_->deselect(P2QSTRING(element.getID()));
+			QPen pen;
+			QBrush brush(Qt::SolidPattern);
+			WhiteboardColor penColor = element.getPenColor();
+			WhiteboardColor brushColor = element.getBrushColor();
+			pen.setColor(QColor(penColor.getRed(), penColor.getGreen(), penColor.getBlue(), penColor.getAlpha()));
+			pen.setWidth(element.getPenWidth());
+			brush.setColor(QColor(brushColor.getRed(), brushColor.getGreen(), brushColor.getBlue(), brushColor.getAlpha()));
+			item->setPen(pen);
+			item->setBrush(brush);
+
+			item->setPos(0,0);
+			QPolygonF polygon;
+			std::vector<std::pair<int, int> >::const_iterator it = element.getPoints().begin();
+			for (; it != element.getPoints().end(); ++it) {
+				polygon.append(QPointF(it->first, it->second));
+			}
+			item->setPolygon(polygon);
+		}
+
+		void visit(WhiteboardTextElement& element) {
+			QGraphicsTextItem* item;
+			QString id = P2QSTRING(element.getID());
+			if (type_ == GView::New) {
+				item = new QGraphicsTextItem;
+				graphicsView_->addItem(item, id, pos_);
+			} else {
+				item = qgraphicsitem_cast<QGraphicsTextItem*>(graphicsView_->getItem(id));
+				graphicsView_->deselect(P2QSTRING(element.getID()));
+			}
+			if (item) {
+				item->setPlainText(P2QSTRING(element.getText()));
+				item->setPos(QPointF(element.getX(), element.getY()));
+				QFont font = item->font();
+				font.setPointSize(element.getSize());
+				item->setFont(font);
+				WhiteboardColor color = element.getColor();
+				item->setDefaultTextColor(QColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()));
+				item->setData(100, id);
+			}
+		}
+
+		void visit(WhiteboardEllipseElement& element) {
+			QRectF rect;
+			QGraphicsEllipseItem* item;
+			QString id = P2QSTRING(element.getID());
+			rect.setTopLeft(QPointF(element.getCX()-element.getRX(), element.getCY()-element.getRY()));
+			rect.setBottomRight(QPointF(element.getCX()+element.getRX(), element.getCY()+element.getRY()));
+			if (type_ == GView::New) {
+				item = new QGraphicsEllipseItem(rect);
+				graphicsView_->addItem(item, id, pos_);
+			} else {
+				item = qgraphicsitem_cast<QGraphicsEllipseItem*>(graphicsView_->getItem(id));
+				item->setRect(rect);
+				item->setPos(0,0);
+				graphicsView_->deselect(P2QSTRING(element.getID()));
+			}
+			QPen pen;
+			QBrush brush(Qt::SolidPattern);
+			WhiteboardColor penColor = element.getPenColor();
+			WhiteboardColor brushColor = element.getBrushColor();
+			pen.setColor(QColor(penColor.getRed(), penColor.getGreen(), penColor.getBlue(), penColor.getAlpha()));
+			pen.setWidth(element.getPenWidth());
+			brush.setColor(QColor(brushColor.getRed(), brushColor.getGreen(), brushColor.getBlue(), brushColor.getAlpha()));
+			item->setPen(pen);
+			item->setBrush(brush);
+			item->setData(100, id);
+		}
+
+	private:
+		GView* graphicsView_;
+		int pos_;
+		GView::Type type_;
+	};
+}
diff --git a/Swift/resources/icons/circle.png b/Swift/resources/icons/circle.png
new file mode 100644
index 0000000..001f4d6
Binary files /dev/null and b/Swift/resources/icons/circle.png differ
diff --git a/Swift/resources/icons/cursor.png b/Swift/resources/icons/cursor.png
new file mode 100644
index 0000000..9e6a7d3
Binary files /dev/null and b/Swift/resources/icons/cursor.png differ
diff --git a/Swift/resources/icons/eraser.png b/Swift/resources/icons/eraser.png
new file mode 100644
index 0000000..b9b7522
Binary files /dev/null and b/Swift/resources/icons/eraser.png differ
diff --git a/Swift/resources/icons/handline.png b/Swift/resources/icons/handline.png
new file mode 100644
index 0000000..a085c14
Binary files /dev/null and b/Swift/resources/icons/handline.png differ
diff --git a/Swift/resources/icons/line.png b/Swift/resources/icons/line.png
new file mode 100644
index 0000000..3b6cf31
Binary files /dev/null and b/Swift/resources/icons/line.png differ
diff --git a/Swift/resources/icons/polygon.png b/Swift/resources/icons/polygon.png
new file mode 100644
index 0000000..da2af14
Binary files /dev/null and b/Swift/resources/icons/polygon.png differ
diff --git a/Swift/resources/icons/rect.png b/Swift/resources/icons/rect.png
new file mode 100644
index 0000000..6bdbea2
Binary files /dev/null and b/Swift/resources/icons/rect.png differ
diff --git a/Swift/resources/icons/text.png b/Swift/resources/icons/text.png
new file mode 100644
index 0000000..ddd76a0
Binary files /dev/null and b/Swift/resources/icons/text.png differ
diff --git a/Swiften/Base/String.cpp b/Swiften/Base/String.cpp
index 7ddf614..242b8e5 100644
--- a/Swiften/Base/String.cpp
+++ b/Swiften/Base/String.cpp
@@ -6,6 +6,8 @@
 
 #include <cassert>
 #include <algorithm>
+#include <sstream>
+#include <iomanip>
 
 #include <Swiften/Base/String.h>
 
@@ -95,4 +97,20 @@ std::vector<std::string> String::split(const std::string& s, char c) {
 	return result;
 }
 
+std::string String::convertIntToHexString(int h) {
+	std::stringstream ss;
+	ss << std::setbase(16);
+	ss << h;
+	return ss.str();
+}
+
+int String::convertHexStringToInt(const std::string& s) {
+	std::stringstream ss;
+	int h;
+	ss << std::setbase(16);
+	ss << s;
+	ss >> h;
+	return h;
+}
+
 }
diff --git a/Swiften/Base/String.h b/Swiften/Base/String.h
index 26cc3f4..de181a1 100644
--- a/Swiften/Base/String.h
+++ b/Swiften/Base/String.h
@@ -29,6 +29,9 @@ namespace Swift {
 			inline bool endsWith(const std::string& s, char c) { 
 				return s.size() > 0 && s[s.size()-1] == c; 
 			}
+
+			std::string convertIntToHexString(int h);
+			int convertHexStringToInt(const std::string& s);
 	};
 
 	class SWIFTEN_API makeString {
diff --git a/Swiften/Client/Client.cpp b/Swiften/Client/Client.cpp
index 4d3ee04..1a6c64b 100644
--- a/Swiften/Client/Client.cpp
+++ b/Swiften/Client/Client.cpp
@@ -29,6 +29,7 @@
 #include <Swiften/Jingle/JingleSessionManager.h>
 #include <Swiften/Network/NetworkFactories.h>
 #include <Swiften/FileTransfer/FileTransferManagerImpl.h>
+#include <Swiften/Whiteboard/WhiteboardSessionManager.h>
 #ifndef SWIFT_EXPERIMENTAL_FT
 #include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h>
 #endif
@@ -68,9 +69,16 @@ Client::Client(const JID& jid, const SafeString& password, NetworkFactories* net
 	
 	jingleSessionManager = new JingleSessionManager(getIQRouter());
 	fileTransferManager = NULL;
+
+	whiteboardSessionManager = NULL;
+#ifdef SWIFT_EXPERIMENTAL_WB
+	whiteboardSessionManager = new WhiteboardSessionManager(getIQRouter(), getStanzaChannel(), presenceOracle, getEntityCapsProvider());
+#endif
 }
 
 Client::~Client() {
+	delete whiteboardSessionManager;
+
 	delete fileTransferManager;
 	delete jingleSessionManager;
 	
@@ -164,4 +172,8 @@ FileTransferManager* Client::getFileTransferManager() const {
 	return fileTransferManager;
 }
 
+WhiteboardSessionManager* Client::getWhiteboardSessionManager() const {
+	return whiteboardSessionManager;
+}
+
 }
diff --git a/Swiften/Client/Client.h b/Swiften/Client/Client.h
index 9652b16..126572a 100644
--- a/Swiften/Client/Client.h
+++ b/Swiften/Client/Client.h
@@ -36,6 +36,7 @@ namespace Swift {
 	class FileTransferManager;
 	class JingleSessionManager;
 	class FileTransferManager;
+	class WhiteboardSessionManager;
 
 	/**
 	 * Provides the core functionality for writing XMPP client software.
@@ -150,6 +151,8 @@ namespace Swift {
 			 * using setCertificateTrustChecker().
 			 */
 			void setAlwaysTrustCertificates();
+
+			WhiteboardSessionManager* getWhiteboardSessionManager() const;
 		
 		public:
 			/**
@@ -185,5 +188,6 @@ namespace Swift {
 			JingleSessionManager* jingleSessionManager;
 			FileTransferManager* fileTransferManager;
 			BlindCertificateTrustChecker* blindCertificateTrustChecker;
+		WhiteboardSessionManager* whiteboardSessionManager;
 	};
 }
diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp
index a4ce079..1683916 100644
--- a/Swiften/Elements/DiscoInfo.cpp
+++ b/Swiften/Elements/DiscoInfo.cpp
@@ -22,6 +22,7 @@ const std::string DiscoInfo::JingleTransportsIBBFeature = std::string("urn:xmpp:
 const std::string DiscoInfo::JingleTransportsS5BFeature = std::string("urn:xmpp:jingle:transports:s5b:1");
 const std::string DiscoInfo::Bytestream = std::string("http://jabber.org/protocol/bytestreams");
 const std::string DiscoInfo::MessageDeliveryReceiptsFeature = std::string("urn:xmpp:receipts");
+const std::string DiscoInfo::WhiteboardFeature = std::string("http://swift.im/whiteboard");
 
 bool DiscoInfo::Identity::operator<(const Identity& other) const {
 	if (category_ == other.category_) {
diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h
index 8add815..3334ac8 100644
--- a/Swiften/Elements/DiscoInfo.h
+++ b/Swiften/Elements/DiscoInfo.h
@@ -33,6 +33,7 @@ namespace Swift {
 			static const std::string JingleTransportsS5BFeature;
 			static const std::string Bytestream;
 			static const std::string MessageDeliveryReceiptsFeature;
+			static const std::string WhiteboardFeature;
 
 			class Identity {
 				public:
diff --git a/Swiften/Elements/Whiteboard/WhiteboardColor.cpp b/Swiften/Elements/Whiteboard/WhiteboardColor.cpp
new file mode 100644
index 0000000..f4ff01a
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardColor.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+#include <Swiften/Base/String.h>
+#include <cstdio>
+#include <iomanip>
+#include <sstream>
+#include <iostream>
+
+namespace Swift {
+	WhiteboardColor::WhiteboardColor() : red_(0), green_(0), blue_(0), alpha_(255) {
+	}
+
+	WhiteboardColor::WhiteboardColor(int red, int green, int blue, int alpha) : red_(red), green_(green), blue_(blue), alpha_(alpha) {
+	}
+
+	WhiteboardColor::WhiteboardColor(const std::string& hex) : alpha_(255) {
+		int value = String::convertHexStringToInt(hex.substr(1));
+		red_ = (value >> 16)&0xFF;
+		green_ = (value >> 8)&0xFF;
+		blue_ = value&0xFF;
+	}
+
+	std::string WhiteboardColor::toHex() const {
+		std::string value = String::convertIntToHexString((red_ << 16) + (green_ << 8) + blue_);
+		while (value.size() < 6) {
+			value.insert(0, "0");
+		}
+		return "#"+value;
+	}
+
+	int WhiteboardColor::getRed() const {
+		return red_;
+	}
+
+	int WhiteboardColor::getGreen() const {
+		return green_;
+	}
+
+	int WhiteboardColor::getBlue() const {
+		return blue_;
+	}
+
+	int WhiteboardColor::getAlpha() const {
+		return alpha_;
+	}
+
+	void WhiteboardColor::setAlpha(int alpha) {
+		alpha_ = alpha;
+	}
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardColor.h b/Swiften/Elements/Whiteboard/WhiteboardColor.h
new file mode 100644
index 0000000..a940338
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardColor.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace Swift {
+	class WhiteboardColor {
+	public:
+		WhiteboardColor();
+		WhiteboardColor(int red, int green, int blue, int alpha = 255);
+		WhiteboardColor(const std::string& hex);
+		std::string toHex() const;
+		int getRed() const;
+		int getGreen() const;
+		int getBlue() const;
+		int getAlpha() const;
+		void setAlpha(int alpha);
+
+	private:
+		int red_, green_, blue_;
+		int alpha_;
+	};
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h b/Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h
new file mode 100644
index 0000000..091ee30
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+
+namespace Swift {
+	class WhiteboardDeleteOperation : public WhiteboardOperation {
+	public:
+		typedef boost::shared_ptr<WhiteboardDeleteOperation> ref;
+	public:
+		~WhiteboardDeleteOperation() {
+		}
+
+		std::string getElementID() const {
+			return elementID_;
+		}
+
+		void setElementID(const std::string& elementID) {
+			elementID_ = elementID;
+		}
+
+	private:
+		std::string elementID_;
+	};
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardElement.h b/Swiften/Elements/Whiteboard/WhiteboardElement.h
new file mode 100644
index 0000000..df774d9
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardElement.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * 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/Whiteboard/WhiteboardElementVisitor.h>
+
+namespace Swift {
+	class WhiteboardElement {
+	public:
+		typedef boost::shared_ptr<WhiteboardElement> ref;
+
+	public:
+		virtual ~WhiteboardElement() {}
+		virtual void accept(WhiteboardElementVisitor& visitor) = 0;
+
+		const std::string& getID() const {
+			return id_;
+		}
+
+		void setID(const std::string& id) {
+			id_ = id;
+		}
+
+	private:
+		std::string id_;
+	};
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h b/Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h
new file mode 100644
index 0000000..413d6cf
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+namespace Swift {
+	class WhiteboardLineElement;
+	class WhiteboardFreehandPathElement;
+	class WhiteboardRectElement;
+	class WhiteboardPolygonElement;
+	class WhiteboardTextElement;
+	class WhiteboardEllipseElement;
+
+	class WhiteboardElementVisitor {
+	public:
+		virtual ~WhiteboardElementVisitor() {}
+		virtual void visit(WhiteboardLineElement& /*element*/) = 0;
+		virtual void visit(WhiteboardFreehandPathElement& /*element*/) = 0;
+		virtual void visit(WhiteboardRectElement& /*element*/) = 0;
+		virtual void visit(WhiteboardPolygonElement& /*element*/) = 0;
+		virtual void visit(WhiteboardTextElement& /*element*/) = 0;
+		virtual void visit(WhiteboardEllipseElement& /*element*/) = 0;
+	};
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h b/Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h
new file mode 100644
index 0000000..0078479
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+
+namespace Swift {
+	class WhiteboardEllipseElement : public WhiteboardElement {
+	public:
+		typedef boost::shared_ptr<WhiteboardEllipseElement> ref;
+	public:
+		WhiteboardEllipseElement(int cx, int cy, int rx, int ry) {
+			cx_ = cx;
+			cy_ = cy;
+			rx_ = rx;
+			ry_ = ry;
+		}
+
+		int getCX() const {
+			return cx_;
+		}
+
+		int getCY() const {
+			return cy_;
+		}
+
+		int getRX() const {
+			return rx_;
+		}
+
+		int getRY() const {
+			return ry_;
+		}
+
+		const WhiteboardColor& getPenColor() const {
+			return penColor_;
+		}
+
+		void setPenColor(const WhiteboardColor& color) {
+			penColor_ = color;
+		}
+
+		const WhiteboardColor& getBrushColor() const {
+			return brushColor_;
+		}
+
+		void setBrushColor(const WhiteboardColor& color) {
+			brushColor_ = color;
+		}
+
+		int getPenWidth() const {
+			return penWidth_;
+		}
+
+		void setPenWidth(const int penWidth) {
+			penWidth_ = penWidth;
+		}
+
+		void accept(WhiteboardElementVisitor& visitor) {
+			visitor.visit(*this);
+		}
+
+	private:
+		int cx_, cy_, rx_, ry_;
+		WhiteboardColor penColor_;
+		WhiteboardColor brushColor_;
+		int penWidth_;
+	};
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h b/Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h
new file mode 100644
index 0000000..bcf3bf9
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+
+#include <vector>
+#include <utility>
+
+namespace Swift {
+	class WhiteboardFreehandPathElement : public WhiteboardElement {
+		typedef std::pair<int, int> Point;
+	public:
+		typedef boost::shared_ptr<WhiteboardFreehandPathElement> ref;
+	public:
+		WhiteboardFreehandPathElement() {
+		}
+
+		void setPoints(const std::vector<Point>& points) {
+			points_ = points;
+		}
+
+		const std::vector<Point>& getPoints() const {
+			return points_;
+		}
+
+		const WhiteboardColor& getColor() const {
+			return color_;
+		}
+
+		void setColor(const WhiteboardColor& color) {
+			color_ = color;
+		}
+
+		int getPenWidth() const {
+			return penWidth_;
+		}
+
+		void setPenWidth(const int penWidth) {
+			penWidth_ = penWidth;
+		}
+
+		void accept(WhiteboardElementVisitor& visitor) {
+			visitor.visit(*this);
+		}
+
+	private:
+		std::vector<Point> points_;
+		WhiteboardColor color_;
+		int penWidth_;
+	};
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h b/Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h
new file mode 100644
index 0000000..8c20044
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+
+namespace Swift {
+	class WhiteboardInsertOperation : public WhiteboardOperation {
+	public:
+		typedef boost::shared_ptr<WhiteboardInsertOperation> ref;
+	public:
+		~WhiteboardInsertOperation() {
+		}
+
+		WhiteboardElement::ref getElement() const {
+			return element_;
+		}
+
+		void setElement(WhiteboardElement::ref element) {
+			element_ = element;
+		}
+
+	private:
+		WhiteboardElement::ref element_;
+	};
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardLineElement.h b/Swiften/Elements/Whiteboard/WhiteboardLineElement.h
new file mode 100644
index 0000000..3c63afc
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardLineElement.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+
+namespace Swift {
+	class WhiteboardLineElement : public WhiteboardElement {
+	public:
+		typedef boost::shared_ptr<WhiteboardLineElement> ref;
+	public:
+		WhiteboardLineElement(int x1, int y1, int x2, int y2) {
+			x1_ = x1;
+			y1_ = y1;
+			x2_ = x2;
+			y2_ = y2;
+		}
+
+		int x1() const {
+			return x1_;
+		}
+
+		int y1() const {
+			return y1_;
+		}
+
+		int x2() const {
+			return x2_;
+		}
+
+		int y2() const {
+			return y2_;
+		}
+
+		const WhiteboardColor& getColor() const {
+			return color_;
+		}
+
+		void setColor(const WhiteboardColor& color) {
+			color_ = color;
+		}
+
+		int getPenWidth() const {
+			return penWidth_;
+		}
+
+		void setPenWidth(const int penWidth) {
+			penWidth_ = penWidth;
+		}
+
+		void accept(WhiteboardElementVisitor& visitor) {
+			visitor.visit(*this);
+		}
+
+	private:
+		int x1_, y1_, x2_, y2_;
+		WhiteboardColor color_;
+		int penWidth_;
+	};
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardOperation.h b/Swiften/Elements/Whiteboard/WhiteboardOperation.h
new file mode 100644
index 0000000..02c6438
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardOperation.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/smart_ptr/shared_ptr.hpp>
+#include <string>
+
+namespace Swift {
+	class WhiteboardOperation {
+	public:
+		typedef boost::shared_ptr<WhiteboardOperation> ref;
+	public:
+		virtual ~WhiteboardOperation(){};
+
+		std::string getID() const {
+			return id_;
+		}
+
+		void setID(const std::string& id) {
+			id_ = id;
+		}
+
+		std::string getParentID() const {
+			return parentID_;
+		}
+
+		void setParentID(const std::string& parentID) {
+			parentID_ = parentID;
+		}
+
+		int getPos() const {
+			return pos_;
+		}
+
+		void setPos(int pos) {
+			pos_ = pos;
+		}
+
+	private:
+		std::string id_;
+		std::string parentID_;
+		int pos_;
+	};
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h b/Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h
new file mode 100644
index 0000000..679ac01
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+
+namespace Swift {
+	class WhiteboardPolygonElement : public WhiteboardElement {
+		typedef std::pair<int, int> Point;
+	public:
+		typedef boost::shared_ptr<WhiteboardPolygonElement> ref;
+	public:
+		WhiteboardPolygonElement() {
+		}
+
+		const std::vector<Point>& getPoints() const {
+			return points_;
+		}
+
+		void setPoints(const std::vector<Point>& points) {
+			points_ = points;
+		}
+
+		const WhiteboardColor& getPenColor() const {
+			return penColor_;
+		}
+
+		void setPenColor(const WhiteboardColor& color) {
+			penColor_ = color;
+		}
+
+		const WhiteboardColor& getBrushColor() const {
+			return brushColor_;
+		}
+
+		void setBrushColor(const WhiteboardColor& color) {
+			brushColor_ = color;
+		}
+
+		int getPenWidth() const {
+			return penWidth_;
+		}
+
+		void setPenWidth(const int penWidth) {
+			penWidth_ = penWidth;
+		}
+
+		void accept(WhiteboardElementVisitor& visitor) {
+			visitor.visit(*this);
+		}
+
+	private:
+		std::vector<Point> points_;
+		WhiteboardColor penColor_;
+		WhiteboardColor brushColor_;
+		int penWidth_;
+	};
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardRectElement.h b/Swiften/Elements/Whiteboard/WhiteboardRectElement.h
new file mode 100644
index 0000000..233b3fa
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardRectElement.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+
+namespace Swift {
+	class WhiteboardRectElement : public WhiteboardElement {
+	public:
+		typedef boost::shared_ptr<WhiteboardRectElement> ref;
+	public:
+		WhiteboardRectElement(int x, int y, int width, int height) {
+			x_ = x;
+			y_ = y;
+			width_ = width;
+			height_ = height;
+		}
+
+		int getX() const {
+			return x_;
+		}
+
+		int getY() const {
+			return y_;
+		}
+
+		int getWidth() const {
+			return width_;
+		}
+
+		int getHeight() const {
+			return height_;
+		}
+
+		const WhiteboardColor& getPenColor() const {
+			return penColor_;
+		}
+
+		void setPenColor(const WhiteboardColor& color) {
+			penColor_ = color;
+		}
+
+		const WhiteboardColor& getBrushColor() const {
+			return brushColor_;
+		}
+
+		void setBrushColor(const WhiteboardColor& color) {
+			brushColor_ = color;
+		}
+
+		int getPenWidth() const {
+			return penWidth_;
+		}
+
+		void setPenWidth(const int penWidth) {
+			penWidth_ = penWidth;
+		}
+
+		void accept(WhiteboardElementVisitor& visitor) {
+			visitor.visit(*this);
+		}
+
+	private:
+		int x_, y_, width_, height_;
+		WhiteboardColor penColor_;
+		WhiteboardColor brushColor_;
+		int penWidth_;
+	};
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardTextElement.h b/Swiften/Elements/Whiteboard/WhiteboardTextElement.h
new file mode 100644
index 0000000..7118ac9
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardTextElement.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+
+namespace Swift {
+	class WhiteboardTextElement : public WhiteboardElement {
+	public:
+		typedef boost::shared_ptr<WhiteboardTextElement> ref;
+	public:
+		WhiteboardTextElement(int x, int y) {
+			x_ = x;
+			y_ = y;
+		}
+
+		void setText(const std::string text) {
+			text_ = text;
+		}
+
+		const std::string& getText() const {
+			return text_;
+		}
+
+		int getX() const {
+			return x_;
+		}
+
+		int getY() const {
+			return y_;
+		}
+
+		const WhiteboardColor& getColor() const {
+			return color_;
+		}
+
+		void setColor(const WhiteboardColor& color) {
+			color_ = color;
+		}
+
+		int getSize() const {
+			return size_;
+		}
+
+		void setSize(const int size) {
+			size_ = size;
+		}
+
+		void accept(WhiteboardElementVisitor& visitor) {
+			visitor.visit(*this);
+		}
+
+	private:
+		int x_, y_;
+		int size_;
+		std::string text_;
+		WhiteboardColor color_;
+	};
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h b/Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h
new file mode 100644
index 0000000..a52a341
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+
+namespace Swift {
+	class WhiteboardUpdateOperation : public WhiteboardOperation {
+	public:
+		typedef boost::shared_ptr<WhiteboardUpdateOperation> ref;
+	public:
+		~WhiteboardUpdateOperation() {
+		}
+
+		WhiteboardElement::ref getElement() const {
+			return element_;
+		}
+
+		void setElement(WhiteboardElement::ref element) {
+			element_ = element;
+		}
+
+		int getNewPos() const {
+			return newPos_;
+		}
+
+		void setNewPos(int newPos) {
+			newPos_ = newPos;
+		}
+
+	private:
+		WhiteboardElement::ref element_;
+		int newPos_;
+	};
+}
diff --git a/Swiften/Elements/WhiteboardPayload.h b/Swiften/Elements/WhiteboardPayload.h
new file mode 100644
index 0000000..ceb2b27
--- /dev/null
+++ b/Swiften/Elements/WhiteboardPayload.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <Swiften/Elements/Payload.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+
+namespace Swift {
+	class WhiteboardPayload : public Payload {
+	public:
+		typedef boost::shared_ptr<WhiteboardPayload> ref;
+
+	public:
+		enum Type {UnknownType, Data, SessionRequest, SessionAccept, SessionTerminate};
+
+		WhiteboardPayload(Type type = WhiteboardPayload::Data) : type_(type) {
+		}
+
+		void setData(const std::string &data) {
+			data_ = data;
+		}
+
+		std::string getData() const {
+			return data_;
+		}
+
+		Type getType() const {
+			return type_;
+		}
+
+		void setType(Type type) {
+			type_ = type;
+		}
+
+		WhiteboardElement::ref getElement() const {
+			return element_;
+		}
+
+		void setElement(WhiteboardElement::ref element) {
+			element_ = element;
+		}
+
+		WhiteboardOperation::ref getOperation() const {
+			return operation_;
+		}
+
+		void setOperation(WhiteboardOperation::ref operation) {
+			operation_ = operation;
+		}
+
+	private:
+		std::string data_;
+		Type type_;
+		WhiteboardElement::ref element_;
+		WhiteboardOperation::ref operation_;
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index 217f278..a40e8f6 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -66,6 +66,7 @@
 #include <Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h>
 #include <Swiften/Parser/PayloadParsers/DeliveryReceiptParserFactory.h>
 #include <Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParserFactory.h>
+#include <Swiften/Parser/PayloadParsers/WhiteboardParser.h>
 
 using namespace boost;
 
@@ -124,6 +125,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
 	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleFileTransferReceivedParser> >("received", "urn:xmpp:jingle:apps:file-transfer:3"));
 	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleFileTransferHashParser> >("checksum"));
 	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<S5BProxyRequestParser> >("query", "http://jabber.org/protocol/bytestreams"));
+	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<WhiteboardParser> >("wb", "http://swift.im/whiteboard"));
 	factories_.push_back(boost::make_shared<DeliveryReceiptParserFactory>());
 	factories_.push_back(boost::make_shared<DeliveryReceiptRequestParserFactory>());
 
diff --git a/Swiften/Parser/PayloadParsers/WhiteboardParser.cpp b/Swiften/Parser/PayloadParsers/WhiteboardParser.cpp
new file mode 100644
index 0000000..09d8de9
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/WhiteboardParser.cpp
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/WhiteboardParser.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardLineElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardRectElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardTextElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h>
+#include <boost/optional.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace Swift {
+	WhiteboardParser::WhiteboardParser() : actualIsText(false), level_(0) {
+	}
+
+	void WhiteboardParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) {
+		if (level_ == 0) {
+			getPayloadInternal()->setType(stringToType(attributes.getAttributeValue("type").get_value_or("")));
+		} else if (level_ == 1) {
+			std::string type = attributes.getAttributeValue("type").get_value_or("");
+			if (type == "insert") {
+				WhiteboardInsertOperation::ref insertOp = boost::make_shared<WhiteboardInsertOperation>();
+				operation = insertOp;
+			} else if (type == "update") {
+				WhiteboardUpdateOperation::ref updateOp = boost::make_shared<WhiteboardUpdateOperation>();
+				std::string move = attributes.getAttributeValue("newpos").get_value_or("0");
+				updateOp->setNewPos(boost::lexical_cast<int>(attributes.getAttributeValue("newpos").get_value_or("0")));
+				operation = updateOp;
+			} else if (type == "delete") {
+				WhiteboardDeleteOperation::ref deleteOp = boost::make_shared<WhiteboardDeleteOperation>();
+				deleteOp->setElementID(attributes.getAttributeValue("elementid").get_value_or(""));
+				operation = deleteOp;
+			}
+			if (operation) {
+				try {
+					operation->setID(attributes.getAttributeValue("id").get_value_or(""));
+					operation->setParentID(attributes.getAttributeValue("parentid").get_value_or(""));
+					operation->setPos(boost::lexical_cast<int>(attributes.getAttributeValue("pos").get_value_or("0")));
+				} catch (boost::bad_lexical_cast&) {
+				}
+			}
+
+		} else if (level_ == 2) {
+			if (element == "line") {
+				int x1 = 0;
+				int y1 = 0;
+				int x2 = 0;
+				int y2 = 0;
+				try {
+					x1 = boost::lexical_cast<int>(attributes.getAttributeValue("x1").get_value_or("0"));
+					y1 = boost::lexical_cast<int>(attributes.getAttributeValue("y1").get_value_or("0"));
+					x2 = boost::lexical_cast<int>(attributes.getAttributeValue("x2").get_value_or("0"));
+					y2 = boost::lexical_cast<int>(attributes.getAttributeValue("y2").get_value_or("0"));
+				} catch (boost::bad_lexical_cast&) {
+				}
+				WhiteboardLineElement::ref whiteboardElement = boost::make_shared<WhiteboardLineElement>(x1, y1, x2, y2);
+
+				WhiteboardColor color(attributes.getAttributeValue("stroke").get_value_or("#000000"));
+				color.setAlpha(opacityToAlpha(attributes.getAttributeValue("opacity").get_value_or("1")));
+				whiteboardElement->setColor(color);
+
+				int penWidth = 1;
+				try {
+					penWidth = boost::lexical_cast<int>(attributes.getAttributeValue("stroke-width").get_value_or("1"));
+				} catch (boost::bad_lexical_cast&) {
+				}
+				whiteboardElement->setPenWidth(penWidth);
+				whiteboardElement->setID(attributes.getAttributeValue("id").get_value_or(""));
+				getPayloadInternal()->setElement(whiteboardElement);
+				wbElement = whiteboardElement;
+			} else if (element == "path") {
+				WhiteboardFreehandPathElement::ref whiteboardElement = boost::make_shared<WhiteboardFreehandPathElement>();
+				std::string pathData = attributes.getAttributeValue("d").get_value_or("");
+				std::vector<std::pair<int, int> > points;
+				if (pathData[0] == 'M') {
+					unsigned int pos = 1;
+					unsigned int npos;
+					int x, y;
+					if (pathData[pos] == ' ') {
+						pos++;
+					}
+					try {
+						npos = pathData.find(' ', pos);
+						x = boost::lexical_cast<int>(pathData.substr(pos, npos-pos));
+						pos = npos+1;
+						npos = pathData.find('L', pos);
+						y = boost::lexical_cast<int>(pathData.substr(pos, npos-pos));
+						pos = npos+1;
+						if (pathData[pos] == ' ') {
+							pos++;
+						}
+						points.push_back(std::pair<int, int>(x, y));
+						while (pos < pathData.size()) {
+							npos = pathData.find(' ', pos);
+							x = boost::lexical_cast<int>(pathData.substr(pos, npos-pos));
+							pos = npos+1;
+							npos = pathData.find(' ', pos);
+							y = boost::lexical_cast<int>(pathData.substr(pos, npos-pos));
+							pos = npos+1;
+							points.push_back(std::pair<int, int>(x, y));
+						}
+					} catch (boost::bad_lexical_cast&) {
+					}
+				}
+				whiteboardElement->setPoints(points);
+
+				int penWidth = 1;
+				try {
+					penWidth = boost::lexical_cast<int>(attributes.getAttributeValue("stroke-width").get_value_or("1"));
+				} catch (boost::bad_lexical_cast&) {
+				}
+				whiteboardElement->setPenWidth(penWidth);
+
+				WhiteboardColor color(attributes.getAttributeValue("stroke").get_value_or("#000000"));
+				color.setAlpha(opacityToAlpha(attributes.getAttributeValue("opacity").get_value_or("1")));
+				whiteboardElement->setColor(color);
+				whiteboardElement->setID(attributes.getAttributeValue("id").get_value_or(""));
+				getPayloadInternal()->setElement(whiteboardElement);
+				wbElement = whiteboardElement;
+			} else if (element == "rect") {
+				int x = 0;
+				int y = 0;
+				int width = 0;
+				int height = 0;
+				try {
+					x = boost::lexical_cast<int>(attributes.getAttributeValue("x").get_value_or("0"));
+					y = boost::lexical_cast<int>(attributes.getAttributeValue("y").get_value_or("0"));
+					width = boost::lexical_cast<int>(attributes.getAttributeValue("width").get_value_or("0"));
+					height = boost::lexical_cast<int>(attributes.getAttributeValue("height").get_value_or("0"));
+				} catch (boost::bad_lexical_cast&) {
+				}
+
+				WhiteboardRectElement::ref whiteboardElement = boost::make_shared<WhiteboardRectElement>(x, y, width, height);
+
+				int penWidth = 1;
+				try {
+					penWidth = boost::lexical_cast<int>(attributes.getAttributeValue("stroke-width").get_value_or("1"));
+				} catch (boost::bad_lexical_cast&) {
+				}
+				whiteboardElement->setPenWidth(penWidth);
+
+				WhiteboardColor penColor(attributes.getAttributeValue("stroke").get_value_or("#000000"));
+				WhiteboardColor brushColor(attributes.getAttributeValue("fill").get_value_or("#000000"));
+				penColor.setAlpha(opacityToAlpha(attributes.getAttributeValue("opacity").get_value_or("1")));
+				brushColor.setAlpha(opacityToAlpha(attributes.getAttributeValue("fill-opacity").get_value_or("1")));
+				whiteboardElement->setPenColor(penColor);
+				whiteboardElement->setBrushColor(brushColor);
+				whiteboardElement->setID(attributes.getAttributeValue("id").get_value_or(""));
+				getPayloadInternal()->setElement(whiteboardElement);
+				wbElement = whiteboardElement;
+			} else if (element == "polygon") {
+				WhiteboardPolygonElement::ref whiteboardElement = boost::make_shared<WhiteboardPolygonElement>();
+
+				std::string pointsData = attributes.getAttributeValue("points").get_value_or("");
+				std::vector<std::pair<int, int> > points;
+				unsigned int pos = 0;
+				unsigned int npos;
+				int x, y;
+				try {
+					while (pos < pointsData.size()) {
+						npos = pointsData.find(',', pos);
+						x = boost::lexical_cast<int>(pointsData.substr(pos, npos-pos));
+						pos = npos+1;
+						npos = pointsData.find(' ', pos);
+						y = boost::lexical_cast<int>(pointsData.substr(pos, npos-pos));
+						pos = npos+1;
+						points.push_back(std::pair<int, int>(x, y));
+					}
+				} catch (boost::bad_lexical_cast&) {
+				}			
+
+				whiteboardElement->setPoints(points);
+
+				int penWidth = 1;
+				try {
+					penWidth = boost::lexical_cast<int>(attributes.getAttributeValue("stroke-width").get_value_or("1"));
+				} catch (boost::bad_lexical_cast&) {
+				}
+				whiteboardElement->setPenWidth(penWidth);
+
+				WhiteboardColor penColor(attributes.getAttributeValue("stroke").get_value_or("#000000"));
+				WhiteboardColor brushColor(attributes.getAttributeValue("fill").get_value_or("#000000"));
+				penColor.setAlpha(opacityToAlpha(attributes.getAttributeValue("opacity").get_value_or("1")));
+				brushColor.setAlpha(opacityToAlpha(attributes.getAttributeValue("fill-opacity").get_value_or("1")));
+				whiteboardElement->setPenColor(penColor);
+				whiteboardElement->setBrushColor(brushColor);
+				whiteboardElement->setID(attributes.getAttributeValue("id").get_value_or(""));
+				getPayloadInternal()->setElement(whiteboardElement);
+				wbElement = whiteboardElement;
+			} else if (element == "text") {
+				int x = 0;
+				int y = 0;
+				try {
+					x = boost::lexical_cast<int>(attributes.getAttributeValue("x").get_value_or("0"));
+					y = boost::lexical_cast<int>(attributes.getAttributeValue("y").get_value_or("0"));
+				} catch (boost::bad_lexical_cast&) {
+				}
+
+				WhiteboardTextElement::ref whiteboardElement = boost::make_shared<WhiteboardTextElement>(x, y);
+
+				actualIsText = true;
+				WhiteboardColor color(attributes.getAttributeValue("fill").get_value_or("#000000"));
+				color.setAlpha(opacityToAlpha(attributes.getAttributeValue("opacity").get_value_or("1")));
+				whiteboardElement->setColor(color);
+
+				int fontSize = 1;
+				try {
+					fontSize = boost::lexical_cast<int>(attributes.getAttributeValue("font-size").get_value_or("12"));
+				} catch (boost::bad_lexical_cast&) {
+				}
+				whiteboardElement->setSize(fontSize);
+				whiteboardElement->setID(attributes.getAttributeValue("id").get_value_or(""));
+				getPayloadInternal()->setElement(whiteboardElement);
+				wbElement = whiteboardElement;
+			} else if (element == "ellipse") {
+				int cx = 0;
+				int cy = 0;
+				int rx = 0;
+				int ry = 0;
+				try {
+					cx = boost::lexical_cast<int>(attributes.getAttributeValue("cx").get_value_or("0"));
+					cy = boost::lexical_cast<int>(attributes.getAttributeValue("cy").get_value_or("0"));
+					rx = boost::lexical_cast<int>(attributes.getAttributeValue("rx").get_value_or("0"));
+					ry = boost::lexical_cast<int>(attributes.getAttributeValue("ry").get_value_or("0"));
+				} catch (boost::bad_lexical_cast&) {
+				}
+
+				WhiteboardEllipseElement::ref whiteboardElement = boost::make_shared<WhiteboardEllipseElement>(cx, cy, rx, ry);
+
+				int penWidth = 1;
+				try {
+					penWidth = boost::lexical_cast<int>(attributes.getAttributeValue("stroke-width").get_value_or("1"));
+				} catch (boost::bad_lexical_cast&) {
+				}
+				whiteboardElement->setPenWidth(penWidth);
+
+				WhiteboardColor penColor(attributes.getAttributeValue("stroke").get_value_or("#000000"));
+				WhiteboardColor brushColor(attributes.getAttributeValue("fill").get_value_or("#000000"));
+				penColor.setAlpha(opacityToAlpha(attributes.getAttributeValue("opacity").get_value_or("1")));
+				brushColor.setAlpha(opacityToAlpha(attributes.getAttributeValue("fill-opacity").get_value_or("1")));
+				whiteboardElement->setPenColor(penColor);
+				whiteboardElement->setBrushColor(brushColor);
+				whiteboardElement->setID(attributes.getAttributeValue("id").get_value_or(""));
+				getPayloadInternal()->setElement(whiteboardElement);
+				wbElement = whiteboardElement;
+			}
+		}
+		++level_;
+	}
+
+	void WhiteboardParser::handleEndElement(const std::string& element, const std::string&) {
+		--level_;
+		if (level_ == 0) {
+			getPayloadInternal()->setData(data_);
+		} else if (level_ == 1) {
+			WhiteboardInsertOperation::ref insertOp = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(operation);
+			if (insertOp) {
+				insertOp->setElement(wbElement);
+			}
+
+			WhiteboardUpdateOperation::ref updateOp = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(operation);
+			if (updateOp) {
+				updateOp->setElement(wbElement);
+			}
+			getPayloadInternal()->setOperation(operation);
+		} else if (level_ == 2) {
+			if (element == "text") {
+				actualIsText = false;
+			}
+		}
+	}
+
+	void WhiteboardParser::handleCharacterData(const std::string& data) {
+		if (level_ == 3 && actualIsText) {
+			WhiteboardTextElement::ref element = boost::dynamic_pointer_cast<WhiteboardTextElement>(getPayloadInternal()->getElement());
+			element->setText(data);
+		}
+	}
+
+	WhiteboardPayload::Type WhiteboardParser::stringToType(const std::string& type) const {
+		if (type == "data") {
+			return WhiteboardPayload::Data;
+		} else if (type == "session-request") {
+			return WhiteboardPayload::SessionRequest;
+		} else if (type == "session-accept") {
+			return WhiteboardPayload::SessionAccept;
+		} else if (type == "session-terminate") {
+			return WhiteboardPayload::SessionTerminate;
+		} else {
+			return WhiteboardPayload::UnknownType;
+		}
+	}
+
+	int WhiteboardParser::opacityToAlpha(std::string opacity) const {
+		int value = 255;
+		if (opacity.find('.') != std::string::npos) {
+			opacity = opacity.substr(opacity.find('.')+1, 2);
+			try {
+				value = boost::lexical_cast<int>(opacity)*255/100;
+			} catch (boost::bad_lexical_cast&) {
+			}
+		}
+		return value;
+	}
+}
diff --git a/Swiften/Parser/PayloadParsers/WhiteboardParser.h b/Swiften/Parser/PayloadParsers/WhiteboardParser.h
new file mode 100644
index 0000000..0368c7c
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/WhiteboardParser.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/WhiteboardPayload.h>
+#include <Swiften/Parser/GenericPayloadParser.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+
+namespace Swift {
+	class WhiteboardParser : public Swift::GenericPayloadParser<WhiteboardPayload> {
+	public:
+		WhiteboardParser();
+
+		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:
+		WhiteboardPayload::Type stringToType(const std::string& type) const;
+		int opacityToAlpha(std::string opacity) const;
+
+	private:
+		bool actualIsText;
+		int level_;
+		std::string data_;
+		WhiteboardElement::ref wbElement;
+		WhiteboardOperation::ref operation;
+	};
+}
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index e4c2778..64e9eb9 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -72,6 +72,7 @@ sources = [
 		"PayloadParsers/S5BProxyRequestParser.cpp",
 		"PayloadParsers/DeliveryReceiptParser.cpp",
 		"PayloadParsers/DeliveryReceiptRequestParser.cpp",
+		"PayloadParsers/WhiteboardParser.cpp",
 		"PlatformXMLParserFactory.cpp",
 		"PresenceParser.cpp",
 		"SerializingParser.cpp",
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 2414d1c..0d14f77 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -189,6 +189,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.cpp",
 			"Serializer/PayloadSerializers/DeliveryReceiptSerializer.cpp",
 			"Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.cpp",
+			"Serializer/PayloadSerializers/WhiteboardSerializer.cpp",
 			"Serializer/PresenceSerializer.cpp",
 			"Serializer/StanzaSerializer.cpp",
 			"Serializer/StreamErrorSerializer.cpp",
@@ -205,6 +206,15 @@ if env["SCONS_STAGE"] == "build" :
 			"StringCodecs/SHA256.cpp",
 			"StringCodecs/MD5.cpp",
 			"StringCodecs/Hexify.cpp",
+			"Whiteboard/WhiteboardResponder.cpp",
+			"Whiteboard/WhiteboardSession.cpp",
+			"Whiteboard/IncomingWhiteboardSession.cpp",
+			"Whiteboard/OutgoingWhiteboardSession.cpp",
+			"Whiteboard/WhiteboardSessionManager.cpp",
+			"Whiteboard/WhiteboardServer.cpp",
+			"Whiteboard/WhiteboardClient.cpp",
+			"Elements/Whiteboard/WhiteboardColor.cpp",
+			"Whiteboard/WhiteboardTransformer.cpp",
 		]
 
 	SConscript(dirs = [
@@ -400,6 +410,8 @@ if env["SCONS_STAGE"] == "build" :
 			File("TLS/UnitTest/ServerIdentityVerifierTest.cpp"),
 			File("TLS/UnitTest/CertificateTest.cpp"),
 			File("VCards/UnitTest/VCardManagerTest.cpp"),
+			File("Whiteboard/UnitTest/WhiteboardServerTest.cpp"),
+			File("Whiteboard/UnitTest/WhiteboardClientTest.cpp"),
 		])
 	
 	# Generate the Swiften header
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index 93fd70f..b4822cd 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -50,6 +50,7 @@
 #include <Swiften/Serializer/PayloadSerializers/SearchPayloadSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/ReplaceSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/LastSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/WhiteboardSerializer.h>
 
 #include <Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.h>
@@ -108,6 +109,7 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() {
 	serializers_.push_back(new SearchPayloadSerializer());
 	serializers_.push_back(new ReplaceSerializer());
 	serializers_.push_back(new LastSerializer());
+	serializers_.push_back(new WhiteboardSerializer());
 	
 	serializers_.push_back(new StreamInitiationFileInfoSerializer());
 	serializers_.push_back(new JingleContentPayloadSerializer());
diff --git a/Swiften/Serializer/PayloadSerializers/WhiteboardSerializer.cpp b/Swiften/Serializer/PayloadSerializers/WhiteboardSerializer.cpp
new file mode 100644
index 0000000..148f643
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/WhiteboardSerializer.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Serializer/PayloadSerializers/WhiteboardSerializer.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/lexical_cast.hpp>
+#include <Swiften/Serializer/XML/XMLTextNode.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h>
+
+namespace Swift {
+	void WhiteboardElementSerializingVisitor::visit(WhiteboardLineElement& line) {
+		element = boost::make_shared<XMLElement>("line");
+		try {
+			element->setAttribute("x1", boost::lexical_cast<std::string>(line.x1()));
+			element->setAttribute("y1", boost::lexical_cast<std::string>(line.y1()));
+			element->setAttribute("x2", boost::lexical_cast<std::string>(line.x2()));
+			element->setAttribute("y2", boost::lexical_cast<std::string>(line.y2()));
+			element->setAttribute("id", line.getID());
+			element->setAttribute("stroke", line.getColor().toHex());
+			element->setAttribute("stroke-width", boost::lexical_cast<std::string>(line.getPenWidth()));
+			element->setAttribute("opacity", alphaToOpacity(line.getColor().getAlpha()));
+		} catch (boost::bad_lexical_cast&) {
+		}
+	}
+
+	void WhiteboardElementSerializingVisitor::visit(WhiteboardFreehandPathElement& path) {
+		element = boost::make_shared<XMLElement>("path");
+		element->setAttribute("id", path.getID());
+		element->setAttribute("stroke", path.getColor().toHex());
+		try {
+			element->setAttribute("stroke-width", boost::lexical_cast<std::string>(path.getPenWidth()));
+			element->setAttribute("opacity", alphaToOpacity(path.getColor().getAlpha()));
+			std::string pathData;
+			if (path.getPoints().size() != 0) {
+				std::vector<std::pair<int, int> >::const_iterator it = path.getPoints().begin();
+				pathData = "M"+boost::lexical_cast<std::string>(it->first)+" "+boost::lexical_cast<std::string>(it->second)+"L";
+				for (; it != path.getPoints().end(); ++it) {
+					pathData += boost::lexical_cast<std::string>(it->first)+" "+boost::lexical_cast<std::string>(it->second)+" ";
+				}
+			}
+			element->setAttribute("d", pathData);
+		} catch (boost::bad_lexical_cast&) {
+		}
+	}
+
+	void WhiteboardElementSerializingVisitor::visit(WhiteboardRectElement& rect) {
+		element = boost::make_shared<XMLElement>("rect");
+		try {
+			element->setAttribute("x", boost::lexical_cast<std::string>(rect.getX()));
+			element->setAttribute("y", boost::lexical_cast<std::string>(rect.getY()));
+			element->setAttribute("width", boost::lexical_cast<std::string>(rect.getWidth()));
+			element->setAttribute("height", boost::lexical_cast<std::string>(rect.getHeight()));
+			element->setAttribute("id", rect.getID());
+			element->setAttribute("stroke", rect.getPenColor().toHex());
+			element->setAttribute("fill", rect.getBrushColor().toHex());;
+			element->setAttribute("stroke-width", boost::lexical_cast<std::string>(rect.getPenWidth()));
+			element->setAttribute("opacity", alphaToOpacity(rect.getPenColor().getAlpha()));
+			element->setAttribute("fill-opacity", alphaToOpacity(rect.getBrushColor().getAlpha()));
+		} catch (boost::bad_lexical_cast&) {
+		}
+	}
+
+	void WhiteboardElementSerializingVisitor::visit(WhiteboardPolygonElement& polygon) {
+		element = boost::make_shared<XMLElement>("polygon");
+		try {
+			element->setAttribute("id", polygon.getID());
+			element->setAttribute("stroke", polygon.getPenColor().toHex());
+			element->setAttribute("fill", polygon.getBrushColor().toHex());;
+			element->setAttribute("stroke-width", boost::lexical_cast<std::string>(polygon.getPenWidth()));
+			element->setAttribute("opacity", alphaToOpacity(polygon.getPenColor().getAlpha()));
+			element->setAttribute("fill-opacity", alphaToOpacity(polygon.getBrushColor().getAlpha()));
+			std::string points;
+			std::vector<std::pair<int, int> >::const_iterator it = polygon.getPoints().begin();
+			for (; it != polygon.getPoints().end(); ++it) {
+				points += boost::lexical_cast<std::string>(it->first)+","+boost::lexical_cast<std::string>(it->second)+" ";
+			}
+			element->setAttribute("points", points);
+		} catch (boost::bad_lexical_cast&) {
+		}
+	}
+
+	void WhiteboardElementSerializingVisitor::visit(WhiteboardTextElement& text) {
+		element = boost::make_shared<XMLElement>("text");
+		try {
+			element->setAttribute("x", boost::lexical_cast<std::string>(text.getX()));
+			element->setAttribute("y", boost::lexical_cast<std::string>(text.getY()));
+			element->setAttribute("font-size", boost::lexical_cast<std::string>(text.getSize()));
+			element->setAttribute("id", text.getID());
+			element->setAttribute("fill", text.getColor().toHex());
+			element->setAttribute("opacity", alphaToOpacity(text.getColor().getAlpha()));
+			element->addNode(boost::make_shared<XMLTextNode>(text.getText()));
+		} catch (boost::bad_lexical_cast&) {
+		}
+	}
+
+ 	void WhiteboardElementSerializingVisitor::visit(WhiteboardEllipseElement& ellipse) {
+		element = boost::make_shared<XMLElement>("ellipse");
+		try {
+			element->setAttribute("cx", boost::lexical_cast<std::string>(ellipse.getCX()));
+			element->setAttribute("cy", boost::lexical_cast<std::string>(ellipse.getCY()));
+			element->setAttribute("rx", boost::lexical_cast<std::string>(ellipse.getRX()));
+			element->setAttribute("ry", boost::lexical_cast<std::string>(ellipse.getRY()));
+			element->setAttribute("id", ellipse.getID());
+			element->setAttribute("stroke", ellipse.getPenColor().toHex());
+			element->setAttribute("fill", ellipse.getBrushColor().toHex());;
+			element->setAttribute("stroke-width", boost::lexical_cast<std::string>(ellipse.getPenWidth()));
+			element->setAttribute("opacity", alphaToOpacity(ellipse.getPenColor().getAlpha()));
+			element->setAttribute("fill-opacity", alphaToOpacity(ellipse.getBrushColor().getAlpha()));
+		} catch (boost::bad_lexical_cast&) {
+		}
+	}
+
+	std::string WhiteboardElementSerializingVisitor::intToStr(const int t) const {
+		std::stringstream ss;
+		ss << t;
+		return ss.str();
+	}
+
+	XMLElement::ref WhiteboardElementSerializingVisitor::getResult() const {
+		return element;
+	}
+
+	std::string WhiteboardElementSerializingVisitor::alphaToOpacity(int alpha) const {
+		int opacity = 100*alpha/254;
+		if (opacity == 100) {
+			return "1";
+		} else {
+			return "."+boost::lexical_cast<std::string>(opacity);
+		}
+	}
+
+	std::string WhiteboardSerializer::serializePayload(boost::shared_ptr<WhiteboardPayload> payload) const {
+		XMLElement element("wb", "http://swift.im/whiteboard");
+		if (payload->getType() == WhiteboardPayload::Data) {
+			XMLElement::ref operationNode = boost::make_shared<XMLElement>("operation");
+			WhiteboardElementSerializingVisitor visitor;
+//			payload->getElement()->accept(visitor);
+			WhiteboardInsertOperation::ref insertOp = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(payload->getOperation());
+			if (insertOp) {
+				try {
+					operationNode->setAttribute("type", "insert");
+					operationNode->setAttribute("pos", boost::lexical_cast<std::string>(insertOp->getPos()));
+					operationNode->setAttribute("id", insertOp->getID());
+					operationNode->setAttribute("parentid", insertOp->getParentID());
+				} catch (boost::bad_lexical_cast&) {
+				}
+				insertOp->getElement()->accept(visitor);
+				operationNode->addNode(visitor.getResult());
+			}
+			WhiteboardUpdateOperation::ref updateOp = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(payload->getOperation());
+			if (updateOp) {
+				try {
+					operationNode->setAttribute("type", "update");
+					operationNode->setAttribute("pos", boost::lexical_cast<std::string>(updateOp->getPos()));
+					operationNode->setAttribute("id", updateOp->getID());
+					operationNode->setAttribute("parentid", updateOp->getParentID());
+					operationNode->setAttribute("newpos", boost::lexical_cast<std::string>(updateOp->getNewPos()));
+				} catch (boost::bad_lexical_cast&) {
+				}
+				updateOp->getElement()->accept(visitor);
+				operationNode->addNode(visitor.getResult());
+
+			}
+
+			WhiteboardDeleteOperation::ref deleteOp = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(payload->getOperation());
+			if (deleteOp) {
+				try {
+					operationNode->setAttribute("type", "delete");
+					operationNode->setAttribute("pos", boost::lexical_cast<std::string>(deleteOp->getPos()));
+					operationNode->setAttribute("id", deleteOp->getID());
+					operationNode->setAttribute("parentid", deleteOp->getParentID());
+					operationNode->setAttribute("elementid", deleteOp->getElementID());
+				} catch (boost::bad_lexical_cast&) {
+				}
+			}
+			element.addNode(operationNode);
+		}
+		element.setAttribute("type", typeToString(payload->getType()));
+		return element.serialize();
+	}
+
+	std::string WhiteboardSerializer::typeToString(WhiteboardPayload::Type type) const {
+		switch (type) {
+			case WhiteboardPayload::Data:
+				return "data";
+			case WhiteboardPayload::SessionRequest:
+				return "session-request";
+			case WhiteboardPayload::SessionAccept:
+				return "session-accept";
+			case WhiteboardPayload::SessionTerminate:
+				return "session-terminate";
+			case WhiteboardPayload::UnknownType:
+				std::cerr << "Serializing unknown action value." << std::endl;
+				return "";
+		}
+		assert(false);
+		return "";
+	}
+}
diff --git a/Swiften/Serializer/PayloadSerializers/WhiteboardSerializer.h b/Swiften/Serializer/PayloadSerializers/WhiteboardSerializer.h
new file mode 100644
index 0000000..d51694f
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/WhiteboardSerializer.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/WhiteboardPayload.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardLineElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardRectElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardTextElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h>
+#include <Swiften/Serializer/GenericPayloadSerializer.h>
+#include <Swiften/Serializer/XML/XMLElement.h>
+
+namespace Swift {
+	class WhiteboardElementSerializingVisitor : public WhiteboardElementVisitor {
+	public:
+		void visit(WhiteboardLineElement& line);
+		void visit(WhiteboardFreehandPathElement& path);
+		void visit(WhiteboardRectElement& rect);
+		void visit(WhiteboardPolygonElement& polygon);
+		void visit(WhiteboardTextElement& text);
+		void visit(WhiteboardEllipseElement& ellipse);
+		XMLElement::ref getResult() const;
+
+	private:
+		std::string intToStr(const int t) const;
+		std::string alphaToOpacity(int alpha) const;
+		
+		XMLElement::ref element;
+	};
+
+	class WhiteboardSerializer : public GenericPayloadSerializer<WhiteboardPayload> {
+	public:
+		std::string serializePayload(boost::shared_ptr<WhiteboardPayload> payload) const;
+
+	private:
+		std::string typeToString(WhiteboardPayload::Type type) const;
+	};
+}
diff --git a/Swiften/Whiteboard/IncomingWhiteboardSession.cpp b/Swiften/Whiteboard/IncomingWhiteboardSession.cpp
new file mode 100644
index 0000000..d64a0d2
--- /dev/null
+++ b/Swiften/Whiteboard/IncomingWhiteboardSession.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/IncomingWhiteboardSession.h>
+#include <Swiften/Elements/WhiteboardPayload.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h>
+
+namespace Swift {
+	IncomingWhiteboardSession::IncomingWhiteboardSession(const JID& jid, IQRouter* router) : WhiteboardSession(jid, router) {
+	}
+
+	IncomingWhiteboardSession::~IncomingWhiteboardSession() {
+	}
+
+	void IncomingWhiteboardSession::accept() {
+		boost::shared_ptr<WhiteboardPayload> payload = boost::make_shared<WhiteboardPayload>(WhiteboardPayload::SessionAccept);
+		boost::shared_ptr<GenericRequest<WhiteboardPayload> > request = boost::make_shared<GenericRequest<WhiteboardPayload> >(IQ::Set, toJID_, payload, router_);
+		request->send();
+		onRequestAccepted(toJID_);
+	}
+
+	void IncomingWhiteboardSession::handleIncomingOperation(WhiteboardOperation::ref operation) {
+		WhiteboardClient::Result pairResult = client.handleServerOperationReceived(operation);
+		if (pairResult.client) {
+			if (pairResult.client->getPos() != -1) {
+				onOperationReceived(pairResult.client);
+			}
+			lastOpID = pairResult.client->getID();
+		}
+
+		if (pairResult.server) {
+			WhiteboardPayload::ref payload = boost::make_shared<WhiteboardPayload>();
+			payload->setOperation(pairResult.server);
+			sendPayload(payload);
+		}
+	}
+
+	void IncomingWhiteboardSession::sendOperation(WhiteboardOperation::ref operation) {
+		operation->setID(idGenerator.generateID());
+		operation->setParentID(lastOpID);
+		lastOpID = operation->getID();
+
+		WhiteboardOperation::ref result = client.handleLocalOperationReceived(operation);
+
+		if (result) {
+			WhiteboardPayload::ref payload = boost::make_shared<WhiteboardPayload>();
+			payload->setOperation(result);
+			sendPayload(payload);
+		}
+	}
+}
diff --git a/Swiften/Whiteboard/IncomingWhiteboardSession.h b/Swiften/Whiteboard/IncomingWhiteboardSession.h
new file mode 100644
index 0000000..beaf267
--- /dev/null
+++ b/Swiften/Whiteboard/IncomingWhiteboardSession.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Whiteboard/WhiteboardClient.h>
+#include <boost/shared_ptr.hpp>
+
+namespace Swift {
+	class IncomingWhiteboardSession : public WhiteboardSession {
+	public:
+		typedef boost::shared_ptr<IncomingWhiteboardSession> ref;
+
+	public:
+		IncomingWhiteboardSession(const JID& jid, IQRouter* router);
+		~IncomingWhiteboardSession();
+
+		void accept();
+
+	private:
+		void handleIncomingOperation(WhiteboardOperation::ref operation);
+		void sendOperation(WhiteboardOperation::ref operation);
+
+		WhiteboardClient client;
+	};
+}
diff --git a/Swiften/Whiteboard/OutgoingWhiteboardSession.cpp b/Swiften/Whiteboard/OutgoingWhiteboardSession.cpp
new file mode 100644
index 0000000..ad81daa
--- /dev/null
+++ b/Swiften/Whiteboard/OutgoingWhiteboardSession.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/OutgoingWhiteboardSession.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/bind.hpp>
+#include <Swiften/Elements/WhiteboardPayload.h>
+
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h>
+
+namespace Swift {
+	OutgoingWhiteboardSession::OutgoingWhiteboardSession(const JID& jid, IQRouter* router) : WhiteboardSession(jid, router) {
+	}
+
+	OutgoingWhiteboardSession::~OutgoingWhiteboardSession() {
+	}
+
+	void OutgoingWhiteboardSession::startSession() {
+		boost::shared_ptr<WhiteboardPayload> payload = boost::make_shared<WhiteboardPayload>(WhiteboardPayload::SessionRequest);
+		boost::shared_ptr<GenericRequest<WhiteboardPayload> > request = boost::make_shared<GenericRequest<WhiteboardPayload> >(IQ::Set, toJID_, payload, router_);
+		request->onResponse.connect(boost::bind(&OutgoingWhiteboardSession::handleRequestResponse, this, _1, _2));
+		request->send();
+	}
+
+	void OutgoingWhiteboardSession::handleRequestResponse(boost::shared_ptr<WhiteboardPayload> /*payload*/, ErrorPayload::ref error) {
+		if (error) {
+			onRequestRejected(toJID_);
+		}
+	}
+
+	void OutgoingWhiteboardSession::handleIncomingOperation(WhiteboardOperation::ref operation) {
+		WhiteboardOperation::ref op = server.handleClientOperationReceived(operation);
+		if (op->getPos() != -1) {
+			onOperationReceived(op);
+		}
+		lastOpID = op->getID();
+
+		WhiteboardPayload::ref payload = boost::make_shared<WhiteboardPayload>();
+		payload->setOperation(op);
+		sendPayload(payload);
+	}
+
+	void OutgoingWhiteboardSession::sendOperation(WhiteboardOperation::ref operation) {
+		operation->setID(idGenerator.generateID());
+		operation->setParentID(lastOpID);
+		lastOpID = operation->getID();
+
+		server.handleLocalOperationReceived(operation);
+		WhiteboardPayload::ref payload = boost::make_shared<WhiteboardPayload>();
+		payload->setOperation(operation);
+		sendPayload(payload);
+	}
+}
diff --git a/Swiften/Whiteboard/OutgoingWhiteboardSession.h b/Swiften/Whiteboard/OutgoingWhiteboardSession.h
new file mode 100644
index 0000000..631f7ba
--- /dev/null
+++ b/Swiften/Whiteboard/OutgoingWhiteboardSession.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Whiteboard/WhiteboardServer.h>
+#include <boost/shared_ptr.hpp>
+#include <Swiften/Queries/GenericRequest.h>
+
+namespace Swift {
+	class OutgoingWhiteboardSession : public WhiteboardSession {
+	public:
+		typedef boost::shared_ptr<OutgoingWhiteboardSession> ref;
+
+	public:
+		OutgoingWhiteboardSession(const JID& jid, IQRouter* router);
+		virtual ~OutgoingWhiteboardSession();
+		void startSession();
+
+	private:
+		void handleRequestResponse(boost::shared_ptr<WhiteboardPayload> /*payload*/, ErrorPayload::ref error);
+		void handleIncomingOperation(WhiteboardOperation::ref operation);
+		void sendOperation(WhiteboardOperation::ref operation);
+
+		WhiteboardServer server;
+	};
+}
diff --git a/Swiften/Whiteboard/UnitTest/WhiteboardClientTest.cpp b/Swiften/Whiteboard/UnitTest/WhiteboardClientTest.cpp
new file mode 100644
index 0000000..7d767d3
--- /dev/null
+++ b/Swiften/Whiteboard/UnitTest/WhiteboardClientTest.cpp
@@ -0,0 +1,676 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * 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 <boost/smart_ptr/make_shared.hpp>
+#include <Swiften/Whiteboard/WhiteboardClient.h>
+#include <Swiften/Whiteboard/Operations/WhiteboardInsertOperation.h>
+#include <Swiften/Whiteboard/Operations/WhiteboardUpdateOperation.h>
+#include <Swiften/Whiteboard/Operations/WhiteboardDeleteOperation.h>
+#include <Swiften/Whiteboard/Elements/WhiteboardEllipseElement.h>
+
+using namespace Swift;
+
+class WhiteboardClientTest : public CppUnit::TestFixture {
+	CPPUNIT_TEST_SUITE(WhiteboardClientTest);
+	CPPUNIT_TEST(testSynchronize_simplestSync);
+	CPPUNIT_TEST(testSynchronize_withoutTranslation);
+	CPPUNIT_TEST(testSynchronize_nonInterrupted);
+	CPPUNIT_TEST(testSynchronize_clientInterruption);
+	CPPUNIT_TEST(testSynchronize_serverInterruption);
+	CPPUNIT_TEST(testSynchronize_nonInterruptedMixOperations);
+	CPPUNIT_TEST(testSynchronize_nonInterruptedMixOperations2);
+	CPPUNIT_TEST_SUITE_END();
+public:
+
+	/*!
+	 *  /\
+	 *  \/
+	 *   \
+	 */
+	void testSynchronize_simplestSync() {
+		WhiteboardClient client;
+		WhiteboardInsertOperation::ref serverOp;
+		serverOp = createInsertOperation("0", "", 0);
+		WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+		CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives first local operation, because it's parented off "0" which exists
+		//in server history and client doesn't wait for any operation ack from server,
+		//so this operation could be send
+		WhiteboardInsertOperation::ref clientOp;
+		clientOp = createInsertOperation("a", "0", 1);
+		WhiteboardEllipseElement::ref aElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setElement(aElement);
+		WhiteboardInsertOperation::ref result;
+		checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+		//Client receives server operation parented off "0", which isn't last client operation
+		//so it have to be transformed against local operations and then transformed value can
+		//be returned to draw
+		serverOp = createInsertOperation("b", "0", 1);
+		WhiteboardEllipseElement::ref bElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		serverOp->setElement(bElement);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.client, "b", "a", 2, bElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives confirmation from the server about processed "a" operation, it had to
+		//be transformed against "b" on the server side to receive operation parented off "b".
+		//There aren't any waiting operations to send to server, this operation should return
+		//nothing
+		serverOp = createInsertOperation("a", "b", 1);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives local operation, it doesn't have to be transformed against anything
+		//but operation returned to send to the server should be parented off last server
+		//operation, which is "b"
+		clientOp = createInsertOperation("c", "b", 3);
+		WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setElement(cElement);
+		checkOperation(client.handleLocalOperationReceived(clientOp), "c", "a", 3, cElement);
+
+		//Client receives confirmation from the server about processed "a" operation, it
+		//should be the same operation as it was sent because server didn't have to
+		//transform it
+		clientOp = createInsertOperation("c", "a", 3);
+		clientOp->setElement(cElement);
+		pairResult = client.handleServerOperationReceived(clientOp);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Results:
+		//Client operations:
+		//ID	pos
+		//0		0
+		//a		1
+		//b		2
+		//c		3
+		//
+		//Server operations:
+		//ID	pos
+		//0		0
+		//b		1
+		//a		1
+		//c		3
+		//
+		//what gives 0abc on both sides
+	}
+
+	/*!
+	 *    /
+	 *   /
+	 *   \
+	 */
+	void testSynchronize_withoutTranslation() {
+		WhiteboardClient client;
+		WhiteboardInsertOperation::ref serverOp;
+		serverOp = createInsertOperation("0", "", 0);
+		WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+		CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives first local operation, because it's parented off "0" which exists
+		//in server history and client doesn't wait for any operation ack from server,
+		//so this operation could be send
+		WhiteboardInsertOperation::ref clientOp = createInsertOperation("c", "0", 1);
+		WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setElement(cElement);
+		checkOperation(client.handleLocalOperationReceived(clientOp), "c", "0", 1, cElement);
+
+		//Client receives second local operation, client didn't receive ack about previous
+		//operation from the server so it can't be send.
+		clientOp = createInsertOperation("d", "c", 2);
+		WhiteboardEllipseElement::ref dElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setElement(dElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientOp));
+
+		//Client receives confirmation about processing "c" operation, it should be the
+		//same as sent operation because it wasn't transformed, client could send now
+		//operation "d"
+		clientOp = createInsertOperation("c", "0", 1);
+		clientOp->setElement(cElement);
+		pairResult = client.handleServerOperationReceived(clientOp);
+		checkOperation(pairResult.server, "d", "c", 2, dElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+		//Client receives confirmation about processing "d", it should be the same as
+		//sent operation. There aren't any operations in queue to send.
+		clientOp = createInsertOperation("d", "c", 2);
+		clientOp->setElement(dElement);
+		pairResult = client.handleServerOperationReceived(clientOp);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives new operation from server, it's parented off "d" which is at
+		//the end of local history so it doesn't have to be transformed, so operation
+		//to pass to window should be the same
+		serverOp = createInsertOperation("e", "d", 3);
+		WhiteboardEllipseElement::ref eElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		serverOp->setElement(eElement);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		WhiteboardInsertOperation::ref result = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client);
+		CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+
+		//Client operations:
+		//ID	pos
+		//0		0
+		//c		1
+		//d		2
+		//e		3
+		//
+		//Server operations:
+		//ID	pos
+		//0		0
+		//c		1
+		//d		2
+		//e		3
+	}
+
+	/*!
+	 *     /\
+	 *    /  \
+	 *    \  /
+	 *     \/
+	 */
+	void testSynchronize_nonInterrupted() {
+		WhiteboardClient client;
+		WhiteboardInsertOperation::ref serverOp;
+		serverOp = createInsertOperation("0", "", 0);
+		WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+		CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives first local operation, because it's parented off "0" which exists
+		//in server history and client doesn't wait for any operation ack from server,
+		//so this operation could be send
+		WhiteboardInsertOperation::ref clientOp;
+		clientOp = createInsertOperation("a", "0", 1);
+		WhiteboardEllipseElement::ref aElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setElement(aElement);
+		checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+		//Client receives second local operation, client didn't receive ack about previous
+		//operation from the server so it can't be send.
+		clientOp = createInsertOperation("b", "a", 2);
+		WhiteboardEllipseElement::ref bElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setElement(bElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientOp));
+
+		//Client receives new operation from server, it should be transformed against
+		//"a" and "b" before adding to local operations history because it's parented off "0".
+		//Because client is waiting for ack of "a", there is no operation to send to server
+		serverOp = createInsertOperation("c", "0", 1);
+		WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		serverOp->setElement(cElement);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.client, "c", "b", 3, cElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives new operation from server, it should be transformed against
+		//results of previous transformations, returned operation should be parented off
+		//"c" existing in local history.
+		//Because client is waiting for ack of "a", there is no operation to send to server
+		serverOp = createInsertOperation("d", "c", 2);
+		WhiteboardEllipseElement::ref dElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		serverOp->setElement(dElement);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.client, "d", "c", 4, dElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives confirmation about processing "a", it should send next operation
+		//to server which is "b", but it should be version parented of transformed "a"
+		serverOp = createInsertOperation("a", "d", 1);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.server, "b", "a", 2, bElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+
+		//Client receives confirmation about processing "b", there aren't any operations
+		//waiting so it should return nothing.
+		serverOp = createInsertOperation("b", "a", 2);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client operations:
+		//ID	pos
+		//0 	0
+		//a 	1
+		//b 	2
+		//c 	3
+		//d 	4
+		//
+		//Server operations:
+		//ID	pos
+		//0 	0
+		//c 	1
+		//d 	2
+		//a 	1
+		//b 	2
+		//
+		//what gives 0abcd on both sides.
+	}
+
+	/*!
+	 *     /\
+	 *    /  \
+	 *    \  /
+	 *    / /
+	 *    \/
+	 */
+	void testSynchronize_clientInterruption() {
+		WhiteboardClient client;
+		WhiteboardInsertOperation::ref serverOp;
+		serverOp = createInsertOperation("0", "", 0);
+		WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+		CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives first local operation, because it's parented off "0" which exists
+		//in server history and client doesn't wait for any operation ack from server,
+		//so this operation could be send
+		WhiteboardInsertOperation::ref clientOp;
+		clientOp = createInsertOperation("a", "0", 1);
+		WhiteboardEllipseElement::ref aElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setElement(aElement);
+		checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+		//Client receives second local operation, client didn't receive ack about previous
+		//operation from the server so it can't be send.
+		clientOp = createInsertOperation("b", "a", 2);
+		WhiteboardEllipseElement::ref bElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setElement(bElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientOp));
+
+		//Client receives new operation from server, it should be transformed against
+		//"a" and "b" before adding to local operations history because it's parented off "0".
+		//Because client is waiting for ack of "a", there is no operation to send to server
+		serverOp = createInsertOperation("c", "0", 1);
+		WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		serverOp->setElement(cElement);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.client, "c", "b", 3, cElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives new local operation, client is still waiting for ack so, it
+		//should return nothing
+		clientOp = createInsertOperation("e", "a", 4);
+		WhiteboardEllipseElement::ref eElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setElement(eElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientOp));
+
+		//Client receives new server operation, to add it to local history it should be transformed
+		//against result of previous transformations and operation "e", returned operation should
+		//be parented off "e", which was last local operation
+		serverOp = createInsertOperation("d", "c", 2);
+		WhiteboardEllipseElement::ref dElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		serverOp->setElement(dElement);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.client, "d", "e", 5, dElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives confirmation about processing "a", it had to be transformed against
+		//"c" and "d" and it is now parented off "d", returned value should be next operation
+		//which have to be send to server("b" parented off server version of "a").
+		serverOp = createInsertOperation("a", "d", 1);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.server, "b", "a", 2, bElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+		//Client receives confirmation about processing "b", it is the same operation as sent because
+		//it didn't have to be transformed, returned value should be next operation
+		//which have to be send to server("e" parented off server version of "b").
+		serverOp = createInsertOperation("b", "a", 2);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.server, "e", "b", 4, eElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+		//Client receives confirmation about processing "b", it is the same operation as sent because
+		//it didn't have to be transformed, there aren't any operations to send so this function returns
+		//nothing
+		serverOp = createInsertOperation("e", "b", 4);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Result:
+		//Client operations:
+		//ID	pos
+		//0 	0
+		//a 	1
+		//b 	2
+		//c 	3
+		//e 	4
+		//d 	5
+		//
+		//Server operations:
+		//0 	0
+		//c 	1
+		//d 	2
+		//a 	1
+		//b 	2
+		//e 	4
+		//what gives 0abced on both sides
+	}
+
+	/*!
+	 *    /\
+	 *   / /
+	 *   \ \
+	 *    \/
+	 */
+	void testSynchronize_serverInterruption() {
+		WhiteboardClient client;
+		WhiteboardInsertOperation::ref serverOp;
+		serverOp = createInsertOperation("0", "", 0);
+		WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+		CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives first local operation, because it's parented off "0" which exists
+		//in server history and client doesn't wait for any operation ack from server,
+		//so this operation could be send
+		WhiteboardInsertOperation::ref clientOp;
+		clientOp = createInsertOperation("a", "0", 1);
+		WhiteboardEllipseElement::ref aElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setElement(aElement);
+		checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+		//Client receives second local operation, client didn't receive ack about previous
+		//operation from the server so it can't be send.
+		clientOp = createInsertOperation("b", "a", 2);
+		WhiteboardEllipseElement::ref bElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setElement(bElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientOp));
+
+		//Client receives new operation from server, it should be transformed against
+		//"a" and "b" before adding to local operations history because it's parented off "0".
+		//Because client is waiting for ack of "a", there is no operation to send to server
+		serverOp = createInsertOperation("c", "0", 1);
+		WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		serverOp->setElement(cElement);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.client, "c", "b", 3, cElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives confirmation about processing "a", it had to be transformed against
+		//"c" and it is now parented off "c", returned value should be next operation
+		//which have to be send to server("b" parented off server version of "a").
+		serverOp = createInsertOperation("a", "c", 1);
+		serverOp->setElement(aElement);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.server, "b", "a", 2, bElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+		//Client receives new server operation, to add it to local history it should be transformed
+		//against result of previous transformation(but only with transformation of "b"), returned
+		//operation should be parented off "c", which was last local operation
+		serverOp = createInsertOperation("d", "a", 3);
+		WhiteboardEllipseElement::ref dElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		serverOp->setElement(dElement);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.client, "d", "c", 4, dElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives confirmation about processing "b", it had to be transformed against
+		//"d" because both operations was parented off server version of "a".
+		//there aren't any operations to send so this function returns nothing.
+		serverOp = createInsertOperation("b", "d", 2);
+		serverOp->setElement(bElement);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client operations:
+		//ID	pos
+		//0 	0
+		//a 	1
+		//b 	2
+		//c 	3
+		//d 	4
+		//
+		//Server operations:
+		//ID	pos
+		//0 	0
+		//c 	1
+		//a 	1
+		//d 	3
+		//b 	2
+		//
+		//what gives 0abcd on both sides
+
+	}
+
+	/*!
+	 *     /\
+	 *    /  \
+	 *    \  /
+	 *     \/
+	 */
+	void testSynchronize_nonInterruptedMixOperations() {
+		WhiteboardClient client;
+		WhiteboardInsertOperation::ref serverOp;
+		serverOp = createInsertOperation("0", "", 0);
+		WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+		CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives first local operation, because it's parented off "0" which exists
+		//in server history and client doesn't wait for any operation ack from server,
+		//so this operation could be send
+		WhiteboardInsertOperation::ref clientOp;
+		clientOp = createInsertOperation("a", "0", 1);
+		WhiteboardEllipseElement::ref aElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setElement(aElement);
+		checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+		//Client receives second local operation, client didn't receive ack about previous
+		//operation from the server so it can't be send.
+		WhiteboardUpdateOperation::ref clientUpdateOp = createUpdateOperation("b", "a", 0);
+		WhiteboardEllipseElement::ref bElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientUpdateOp->setElement(bElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientUpdateOp));
+
+		//Client receives new operation from server, it should be transformed against
+		//"a" and "b" before adding to local operations history because it's parented off "0".
+		//Because client is waiting for ack of "a", there is no operation to send to server
+		WhiteboardUpdateOperation::ref serverUpdateOp = createUpdateOperation("c", "0", 0);
+		WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		serverUpdateOp->setElement(cElement);
+		pairResult = client.handleServerOperationReceived(serverUpdateOp);
+		checkOperation(pairResult.client, "c", "b", 0, cElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives new operation from server, it should be transformed against
+		//results of previous transformations, returned operation should be parented off
+		//"c" existing in local history.
+		//Because client is waiting for ack of "a", there is no operation to send to server
+		serverOp = createInsertOperation("d", "c", 1);
+		WhiteboardEllipseElement::ref dElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		serverOp->setElement(dElement);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.client, "d", "c", 2, dElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives confirmation about processing "a", it should send next operation
+		//to server which is "b", but it should be version parented of transformed "a"
+		serverOp = createInsertOperation("a", "d", 1);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.server, "b", "a", 0, cElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+
+		//Client receives confirmation about processing "b", there aren't any operations
+		//waiting so it should return nothing.
+		serverUpdateOp = createUpdateOperation("b", "a", 0);
+		pairResult = client.handleServerOperationReceived(serverUpdateOp);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client operations:
+		//ID	pos
+		//0 	0
+		//a 	1
+		//b 	2
+		//c 	3
+		//d 	4
+		//
+		//Server operations:
+		//ID	pos
+		//0 	0
+		//c 	1
+		//d 	2
+		//a 	1
+		//b 	2
+		//
+		//what gives 0abcd on both sides.
+	}
+
+	/*!
+	 *     /\
+	 *    /  \
+	 *    \  /
+	 *     \/
+	 */
+	void testSynchronize_nonInterruptedMixOperations2() {
+		WhiteboardClient client;
+		WhiteboardInsertOperation::ref serverOp;
+		serverOp = createInsertOperation("0", "", 0);
+		WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+		CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		serverOp = createInsertOperation("1", "0", 1);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+		//Client receives first local operation, because it's parented off "0" which exists
+		//in server history and client doesn't wait for any operation ack from server,
+		//so this operation could be send
+		WhiteboardInsertOperation::ref clientOp;
+		WhiteboardUpdateOperation::ref clientUpdateOp;
+		WhiteboardDeleteOperation::ref clientDeleteOp;
+		clientUpdateOp = createUpdateOperation("a", "1", 0);
+		WhiteboardEllipseElement::ref aElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientUpdateOp->setElement(aElement);
+		checkOperation(client.handleLocalOperationReceived(clientUpdateOp), "a", "1", 0, aElement);
+
+		//Client receives second local operation, client didn't receive ack about previous
+		//operation from the server so it can't be send.
+		clientDeleteOp = createDeleteOperation("b", "a", 1);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientDeleteOp));
+
+		//Client receives new operation from server, it should be transformed against
+		//"a" and "b" before adding to local operations history because it's parented off "0".
+		//Because client is waiting for ack of "a", there is no operation to send to server
+		serverOp = createInsertOperation("c", "1", 2);
+		WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		serverOp->setElement(cElement);
+		pairResult = client.handleServerOperationReceived(serverOp);
+		checkOperation(pairResult.client, "c", "b", 1, cElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives new operation from server, it should be transformed against
+		//results of previous transformations, returned operation should be parented off
+		//"c" existing in local history.
+		//Because client is waiting for ack of "a", there is no operation to send to server
+		WhiteboardUpdateOperation::ref serverUpdateOp = createUpdateOperation("d", "c", 0);
+		WhiteboardEllipseElement::ref dElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		serverUpdateOp->setElement(dElement);
+		pairResult = client.handleServerOperationReceived(serverUpdateOp);
+		checkOperation(pairResult.client, "d", "c", 0, dElement);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client receives confirmation about processing "a", it should send next operation
+		//to server which is "b", but it should be version parented of transformed "a"
+		serverUpdateOp = createUpdateOperation("a", "d", 0);
+		pairResult = client.handleServerOperationReceived(serverUpdateOp);
+		checkOperation(pairResult.server, "b", "a", 1);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+
+		//Client receives confirmation about processing "b", there aren't any operations
+		//waiting so it should return nothing.
+		WhiteboardDeleteOperation::ref serverDeleteOp = createDeleteOperation("b", "a", 0);
+		pairResult = client.handleServerOperationReceived(serverDeleteOp);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+		CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+		//Client operations:
+		//ID	pos
+		//0 	0
+		//a 	1
+		//b 	2
+		//c 	3
+		//d 	4
+		//
+		//Server operations:
+		//ID	pos
+		//0 	0
+		//c 	1
+		//d 	2
+		//a 	1
+		//b 	2
+		//
+		//what gives 0abcd on both sides.
+	}
+
+
+	WhiteboardInsertOperation::ref createInsertOperation(std::string id, std::string parent, int pos) {
+		WhiteboardInsertOperation::ref operation = boost::make_shared<WhiteboardInsertOperation>();
+		operation->setParentID(parent);
+		operation->setID(id);
+		operation->setPos(pos);
+		return operation;
+	}
+
+	WhiteboardUpdateOperation::ref createUpdateOperation(std::string id, std::string parent, int pos) {
+		WhiteboardUpdateOperation::ref operation = boost::make_shared<WhiteboardUpdateOperation>();
+		operation->setParentID(parent);
+		operation->setID(id);
+		operation->setPos(pos);
+		return operation;
+	}
+
+	WhiteboardDeleteOperation::ref createDeleteOperation(std::string id, std::string parent, int pos) {
+		WhiteboardDeleteOperation::ref operation = boost::make_shared<WhiteboardDeleteOperation>();
+		operation->setParentID(parent);
+		operation->setID(id);
+		operation->setPos(pos);
+		return operation;
+	}
+
+	void checkOperation(WhiteboardOperation::ref operation, std::string id, std::string parent, int pos = -1, WhiteboardElement::ref element = WhiteboardElement::ref()) {
+		CPPUNIT_ASSERT_EQUAL(id, operation->getID());
+		CPPUNIT_ASSERT_EQUAL(parent, operation->getParentID());
+		if (pos != -1) {
+			CPPUNIT_ASSERT_EQUAL(pos, operation->getPos());
+		}
+
+		if (element) {
+			WhiteboardInsertOperation::ref insertOp = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(operation);
+			if (insertOp) {
+				CPPUNIT_ASSERT_EQUAL(element, insertOp->getElement());
+			}
+
+			WhiteboardUpdateOperation::ref updateOp = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(operation);
+			if (updateOp) {
+				CPPUNIT_ASSERT_EQUAL(element, updateOp->getElement());
+			}
+		}
+	}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(WhiteboardClientTest);
diff --git a/Swiften/Whiteboard/UnitTest/WhiteboardServerTest.cpp b/Swiften/Whiteboard/UnitTest/WhiteboardServerTest.cpp
new file mode 100644
index 0000000..563be54
--- /dev/null
+++ b/Swiften/Whiteboard/UnitTest/WhiteboardServerTest.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * 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 <boost/smart_ptr/make_shared.hpp>
+#include <Swiften/Whiteboard/WhiteboardServer.h>
+#include <Swiften/Whiteboard/Operations/WhiteboardInsertOperation.h>
+#include <Swiften/Whiteboard/Operations/WhiteboardDeleteOperation.h>
+#include <Swiften/Whiteboard/Operations/WhiteboardUpdateOperation.h>
+#include <Swiften/Whiteboard/Elements/WhiteboardEllipseElement.h>
+
+using namespace Swift;
+
+class WhiteboardServerTest : public CppUnit::TestFixture {
+	CPPUNIT_TEST_SUITE(WhiteboardServerTest);
+	CPPUNIT_TEST(testSimpleOp);
+	CPPUNIT_TEST(testSimpleOp1);
+	CPPUNIT_TEST(testSimpleOp2);
+	CPPUNIT_TEST(testFewSimpleOps);
+	CPPUNIT_TEST_SUITE_END();
+public:
+	void testSimpleOp() {
+		WhiteboardServer server;
+		WhiteboardInsertOperation::ref firstOp = boost::make_shared<WhiteboardInsertOperation>();
+		firstOp->setID("0");
+		server.handleLocalOperationReceived(firstOp);
+		WhiteboardInsertOperation::ref serverOp = boost::make_shared<WhiteboardInsertOperation>();
+		serverOp->setID("b");
+		serverOp->setParentID("0");
+		serverOp->setPos(1);
+		server.handleLocalOperationReceived(serverOp);
+		WhiteboardInsertOperation::ref clientOp = boost::make_shared<WhiteboardInsertOperation>();
+		WhiteboardEllipseElement::ref clientElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setID("a");
+		clientOp->setParentID("0");
+		clientOp->setPos(1);
+		clientOp->setElement(clientElement);
+		WhiteboardInsertOperation::ref op = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(server.handleClientOperationReceived(clientOp));
+		CPPUNIT_ASSERT_EQUAL(std::string("b"), op->getParentID());
+		CPPUNIT_ASSERT_EQUAL(std::string("a"), op->getID());
+		CPPUNIT_ASSERT_EQUAL(1, op->getPos());
+		CPPUNIT_ASSERT_EQUAL(clientElement, boost::dynamic_pointer_cast<WhiteboardEllipseElement>(op->getElement()));
+	}
+
+	void testSimpleOp1() {
+		WhiteboardServer server;
+		WhiteboardInsertOperation::ref firstOp = boost::make_shared<WhiteboardInsertOperation>();
+		firstOp->setID("0");
+		server.handleLocalOperationReceived(firstOp);
+		WhiteboardDeleteOperation::ref serverOp = boost::make_shared<WhiteboardDeleteOperation>();
+		serverOp->setID("b");
+		serverOp->setParentID("0");
+		serverOp->setPos(1);
+		server.handleLocalOperationReceived(serverOp);
+		WhiteboardUpdateOperation::ref clientOp = boost::make_shared<WhiteboardUpdateOperation>();
+		WhiteboardEllipseElement::ref clientElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setID("a");
+		clientOp->setParentID("0");
+		clientOp->setPos(1);
+		clientOp->setElement(clientElement);
+		WhiteboardDeleteOperation::ref op = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(server.handleClientOperationReceived(clientOp));
+		CPPUNIT_ASSERT_EQUAL(std::string("b"), op->getParentID());
+		CPPUNIT_ASSERT_EQUAL(std::string("a"), op->getID());
+		CPPUNIT_ASSERT_EQUAL(-1, op->getPos());
+	}
+
+	void testSimpleOp2() {
+		WhiteboardServer server;
+		WhiteboardInsertOperation::ref firstOp = boost::make_shared<WhiteboardInsertOperation>();
+		firstOp->setID("0");
+		server.handleLocalOperationReceived(firstOp);
+		WhiteboardUpdateOperation::ref serverOp = boost::make_shared<WhiteboardUpdateOperation>();
+		serverOp->setID("b");
+		serverOp->setParentID("0");
+		serverOp->setPos(1);
+		server.handleLocalOperationReceived(serverOp);
+		WhiteboardDeleteOperation::ref clientOp = boost::make_shared<WhiteboardDeleteOperation>();
+		clientOp->setID("a");
+		clientOp->setParentID("0");
+		clientOp->setPos(1);
+		WhiteboardDeleteOperation::ref op = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(server.handleClientOperationReceived(clientOp));
+		CPPUNIT_ASSERT_EQUAL(std::string("b"), op->getParentID());
+		CPPUNIT_ASSERT_EQUAL(std::string("a"), op->getID());
+		CPPUNIT_ASSERT_EQUAL(1, op->getPos());
+	}
+
+
+	void testFewSimpleOps() {
+		WhiteboardServer server;
+		WhiteboardInsertOperation::ref firstOp = boost::make_shared<WhiteboardInsertOperation>();
+		firstOp->setID("0");
+		server.handleLocalOperationReceived(firstOp);
+		WhiteboardInsertOperation::ref serverOp = boost::make_shared<WhiteboardInsertOperation>();
+		serverOp->setID("a");
+		serverOp->setParentID("0");
+		serverOp->setPos(1);
+		server.handleLocalOperationReceived(serverOp);
+		serverOp = boost::make_shared<WhiteboardInsertOperation>();
+		serverOp->setID("b");
+		serverOp->setParentID("a");
+		serverOp->setPos(2);
+		server.handleLocalOperationReceived(serverOp);
+		serverOp = boost::make_shared<WhiteboardInsertOperation>();
+		serverOp->setID("c");
+		serverOp->setParentID("b");
+		serverOp->setPos(3);
+		server.handleLocalOperationReceived(serverOp);
+		WhiteboardInsertOperation::ref clientOp = boost::make_shared<WhiteboardInsertOperation>();
+		WhiteboardEllipseElement::ref clientElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+		clientOp->setID("d");
+		clientOp->setParentID("0");
+		clientOp->setPos(1);
+		clientOp->setElement(clientElement);
+		WhiteboardInsertOperation::ref op = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(server.handleClientOperationReceived(clientOp));
+		CPPUNIT_ASSERT_EQUAL(std::string("c"), op->getParentID());
+		CPPUNIT_ASSERT_EQUAL(std::string("d"), op->getID());
+		CPPUNIT_ASSERT_EQUAL(1, op->getPos());
+		CPPUNIT_ASSERT_EQUAL(clientElement, boost::dynamic_pointer_cast<WhiteboardEllipseElement>(op->getElement()));
+	}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(WhiteboardServerTest);
diff --git a/Swiften/Whiteboard/WhiteboardClient.cpp b/Swiften/Whiteboard/WhiteboardClient.cpp
new file mode 100644
index 0000000..4dacc90
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardClient.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/WhiteboardClient.h>
+#include <Swiften/Whiteboard/WhiteboardTransformer.h>
+#include <boost/smart_ptr/make_shared.hpp>
+
+namespace Swift {
+	WhiteboardOperation::ref WhiteboardClient::handleLocalOperationReceived(WhiteboardOperation::ref operation) {
+		localOperations_.push_back(operation);
+
+		WhiteboardOperation::ref op;
+		WhiteboardInsertOperation::ref insertOp = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(operation);
+		if (insertOp) {
+			op = boost::make_shared<WhiteboardInsertOperation>(*insertOp);
+		}
+		WhiteboardUpdateOperation::ref updateOp = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(operation);
+		if (updateOp) {
+			op = boost::make_shared<WhiteboardUpdateOperation>(*updateOp);
+		}
+		WhiteboardDeleteOperation::ref deleteOp = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(operation);
+		if (deleteOp) {
+			op = boost::make_shared<WhiteboardDeleteOperation>(*deleteOp);
+		}
+
+		if (bridge_.size() > 0) {
+			op->setParentID(bridge_.back()->getID());
+		}
+		bridge_.push_back(op);
+
+		if (lastSentOperationID_.empty())
+		{
+			WhiteboardInsertOperation::ref insertOp = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(operation);
+			if (insertOp) {
+				op = boost::make_shared<WhiteboardInsertOperation>(*insertOp);
+			}
+			WhiteboardUpdateOperation::ref updateOp = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(operation);
+			if (updateOp) {
+				op = boost::make_shared<WhiteboardUpdateOperation>(*updateOp);
+			}
+			WhiteboardDeleteOperation::ref deleteOp = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(operation);
+			if (deleteOp) {
+				op = boost::make_shared<WhiteboardDeleteOperation>(*deleteOp);
+			}
+
+
+			if (serverOperations_.size() > 0) {
+				op->setParentID(serverOperations_.back()->getID());
+			}
+			lastSentOperationID_ = operation->getID();
+			return op;
+		} else {
+			return WhiteboardOperation::ref();
+		}
+	}
+
+	WhiteboardClient::Result WhiteboardClient::handleServerOperationReceived(WhiteboardOperation::ref operation) {
+		serverOperations_.push_back(operation);
+		Result result;
+//		if (localOperations_.empty()) {// || localOperations_.back()->getID() == operation->getParentID()) {
+		//Situation where client and server are in sync
+		if (localOperations_.size() == serverOperations_.size()-1) {
+			localOperations_.push_back(operation);
+//			clientOp = operation;
+			result.client = operation;
+		} else if (lastSentOperationID_ == operation->getID()) {
+			//Client received confirmation about own operation and it sends next operation to server
+			if (bridge_.size() > 0 && lastSentOperationID_ == bridge_.front()->getID()) {
+				bridge_.erase(bridge_.begin());
+			}
+
+			if (bridge_.size() > 0 && (bridge_.front())->getParentID() == lastSentOperationID_) {
+				lastSentOperationID_ = (bridge_.front())->getID();
+				result.server = bridge_.front();
+			}
+			if (!result.server) {
+				lastSentOperationID_.clear();
+			}
+		} else {
+			std::list<WhiteboardOperation::ref>::iterator it = bridge_.begin();
+			std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> opPair;
+			WhiteboardOperation::ref temp;
+			opPair = WhiteboardTransformer::transform(*it, operation);
+			temp = opPair.first;
+
+			*it = opPair.second;
+			std::string previousID = (*it)->getID();
+			++it;
+			for (; it != bridge_.end(); ++it) {
+				opPair = WhiteboardTransformer::transform(*it, temp);
+				temp = opPair.first;
+				*it = opPair.second;
+				(*it)->setParentID(previousID);
+				previousID = (*it)->getID();
+			}
+
+			temp->setParentID(localOperations_.back()->getID());
+			localOperations_.push_back(temp);
+			result.client = temp;
+		}
+
+		return result;
+	}
+
+	void WhiteboardClient::print() {
+		std::list<WhiteboardOperation::ref>::iterator it;
+		std::cout << "Client" << std::endl;
+		for(it = localOperations_.begin(); it != localOperations_.end(); ++it) {
+			std::cout << (*it)->getID() << " " << (*it)->getPos() << std::endl;
+		}
+
+		std::cout << "Server" << std::endl;
+		for(it = serverOperations_.begin(); it != serverOperations_.end(); ++it) {
+			std::cout << (*it)->getID() << " " << (*it)->getPos() << std::endl;
+		}
+	}
+}
diff --git a/Swiften/Whiteboard/WhiteboardClient.h b/Swiften/Whiteboard/WhiteboardClient.h
new file mode 100644
index 0000000..388f948
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardClient.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+#include <list>
+#include <utility>
+
+namespace Swift {
+	class WhiteboardClient {
+	public:
+		struct Result {
+			WhiteboardOperation::ref client;
+			WhiteboardOperation::ref server;
+		};
+		/*!
+		 * @return Operation to send
+		 */   	
+		WhiteboardOperation::ref handleLocalOperationReceived(WhiteboardOperation::ref operation);
+		/*!
+		 * @return pair.first-element to handle locally, pair.second-element to send to server
+		 */
+		Result handleServerOperationReceived(WhiteboardOperation::ref operation);
+		void print();
+
+	private:
+		std::list<WhiteboardOperation::ref> localOperations_;
+		std::list<WhiteboardOperation::ref> serverOperations_;
+		std::list<WhiteboardOperation::ref> bridge_;
+		std::string lastSentOperationID_;
+	};
+}
diff --git a/Swiften/Whiteboard/WhiteboardResponder.cpp b/Swiften/Whiteboard/WhiteboardResponder.cpp
new file mode 100644
index 0000000..f72861f
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardResponder.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/WhiteboardResponder.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <Swiften/Whiteboard/WhiteboardSessionManager.h>
+#include <Swiften/Whiteboard/IncomingWhiteboardSession.h>
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Queries/IQRouter.h>
+
+namespace Swift {
+	WhiteboardResponder::WhiteboardResponder(WhiteboardSessionManager* sessionManager, IQRouter* router) : SetResponder<WhiteboardPayload>(router), sessionManager_(sessionManager), router_(router) {
+	}
+
+	bool WhiteboardResponder::handleSetRequest(const JID& from, const JID& /*to*/, const std::string& id, boost::shared_ptr<WhiteboardPayload> payload) {
+		if (payload->getType() == WhiteboardPayload::SessionRequest) {
+			if (sessionManager_->getSession(from)) {
+				sendError(from, id, ErrorPayload::Conflict, ErrorPayload::Cancel);
+			} else {
+				sendResponse(from, id, boost::shared_ptr<WhiteboardPayload>());
+				IncomingWhiteboardSession::ref session = boost::make_shared<IncomingWhiteboardSession>(from, router_);
+				sessionManager_->handleIncomingSession(session);
+			}
+		} else {
+			sendResponse(from, id, boost::shared_ptr<WhiteboardPayload>());
+			WhiteboardSession::ref session = sessionManager_->getSession(from);
+			if (session != NULL) {
+				session->handleIncomingAction(payload);
+			}
+		}
+		return true;
+	}
+}
diff --git a/Swiften/Whiteboard/WhiteboardResponder.h b/Swiften/Whiteboard/WhiteboardResponder.h
new file mode 100644
index 0000000..c05be23
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardResponder.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Queries/SetResponder.h>
+#include <Swiften/Elements/WhiteboardPayload.h>
+
+namespace Swift {
+	class IQRouter;
+	class WhiteboardSessionManager;
+
+	class WhiteboardResponder : public SetResponder<WhiteboardPayload> {
+	public:
+		WhiteboardResponder(WhiteboardSessionManager* sessionManager, IQRouter* router);
+		bool handleSetRequest(const JID& from, const JID& /*to*/, const std::string& id, boost::shared_ptr<WhiteboardPayload> payload);
+
+	private:
+		WhiteboardSessionManager* sessionManager_;
+		IQRouter* router_;
+	};
+}
diff --git a/Swiften/Whiteboard/WhiteboardServer.cpp b/Swiften/Whiteboard/WhiteboardServer.cpp
new file mode 100644
index 0000000..384372b
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardServer.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/WhiteboardServer.h>
+#include <Swiften/Whiteboard/WhiteboardTransformer.h>
+
+namespace Swift {
+	void WhiteboardServer::handleLocalOperationReceived(WhiteboardOperation::ref operation) {
+		operations_.push_back(operation);
+	}
+
+	WhiteboardOperation::ref WhiteboardServer::handleClientOperationReceived(WhiteboardOperation::ref newOperation) {
+		std::list<WhiteboardOperation::ref>::reverse_iterator it;
+		if (operations_.size() == 0 || newOperation->getParentID() == operations_.back()->getID()) {
+			operations_.push_back(newOperation);
+			return newOperation;
+		}
+		for (it = operations_.rbegin(); it != operations_.rend(); ++it) {
+			WhiteboardOperation::ref operation = *it;
+			while (newOperation->getParentID() == operation->getParentID()) {
+				std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> tResult = WhiteboardTransformer::transform(newOperation, operation);
+
+				if (it == operations_.rbegin()) {
+					operations_.push_back(tResult.second);
+					return tResult.second;
+				} else {
+					newOperation = tResult.second;
+					--it;
+					operation = *it;
+				}
+
+			}
+		}
+		return WhiteboardOperation::ref();
+	}
+
+	void WhiteboardServer::print() {
+		std::list<WhiteboardOperation::ref>::iterator it;
+		std::cout << "Server:" << std::endl;
+		for(it = operations_.begin(); it != operations_.end(); ++it) {
+			std::cout << (*it)->getID() << " " << (*it)->getPos() << std::endl;
+		}
+	}
+
+}
diff --git a/Swiften/Whiteboard/WhiteboardServer.h b/Swiften/Whiteboard/WhiteboardServer.h
new file mode 100644
index 0000000..658254b
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardServer.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+
+#include <list>
+
+namespace Swift {
+	class WhiteboardServer {
+	public:
+		void handleLocalOperationReceived(WhiteboardOperation::ref operation);
+		WhiteboardOperation::ref handleClientOperationReceived(WhiteboardOperation::ref operation);
+		void print();
+
+	private:
+		std::list<WhiteboardOperation::ref> operations_;
+	};
+}
diff --git a/Swiften/Whiteboard/WhiteboardSession.cpp b/Swiften/Whiteboard/WhiteboardSession.cpp
new file mode 100644
index 0000000..cffcf07
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardSession.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/Elements/WhiteboardPayload.h>
+#include <Swiften/Elements/ErrorPayload.h>
+
+#include <iostream>
+
+namespace Swift {
+	WhiteboardSession::WhiteboardSession(const JID& jid, IQRouter* router) : toJID_(jid), router_(router) {
+	}
+
+	WhiteboardSession::~WhiteboardSession() {
+	}
+
+	void WhiteboardSession::handleIncomingAction(boost::shared_ptr<WhiteboardPayload> payload) {
+		switch (payload->getType()) {
+			case WhiteboardPayload::Data:
+				handleIncomingOperation(payload->getOperation());
+				return;
+			case WhiteboardPayload::SessionAccept:
+				onRequestAccepted(toJID_);
+				return;
+			case WhiteboardPayload::SessionTerminate:
+				onSessionTerminated(toJID_);
+				return;
+
+			//handled elsewhere
+			case WhiteboardPayload::SessionRequest:
+
+			case WhiteboardPayload::UnknownType:
+				return;
+		}
+	}
+
+	void WhiteboardSession::sendElement(const WhiteboardElement::ref element) {
+		boost::shared_ptr<WhiteboardPayload> payload = boost::make_shared<WhiteboardPayload>();
+		payload->setElement(element);
+		boost::shared_ptr<GenericRequest<WhiteboardPayload> > request = boost::make_shared<GenericRequest<WhiteboardPayload> >(IQ::Set, toJID_, payload, router_);
+		request->send();
+	}
+
+	void WhiteboardSession::sendPayload(boost::shared_ptr<WhiteboardPayload> payload) {
+		boost::shared_ptr<GenericRequest<WhiteboardPayload> > request = boost::make_shared<GenericRequest<WhiteboardPayload> >(IQ::Set, toJID_, payload, router_);
+		request->send();
+	}
+
+	void WhiteboardSession::cancel() {
+		if (router_->isAvailable()) {
+			boost::shared_ptr<WhiteboardPayload> payload = boost::make_shared<WhiteboardPayload>(WhiteboardPayload::SessionTerminate);
+			boost::shared_ptr<GenericRequest<WhiteboardPayload> > request = boost::make_shared<GenericRequest<WhiteboardPayload> >(IQ::Set, toJID_, payload, router_);
+			request->send();
+		}
+		onSessionTerminated(toJID_);
+	}
+
+	const JID& WhiteboardSession::getTo() const {
+		return toJID_;
+	}
+}
diff --git a/Swiften/Whiteboard/WhiteboardSession.h b/Swiften/Whiteboard/WhiteboardSession.h
new file mode 100644
index 0000000..39fa341
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardSession.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/JID/JID.h>
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Base/IDGenerator.h>
+#include <Swiften/Queries/GenericRequest.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+
+namespace Swift {
+	class IQRouter;
+	class ErrorPayload;
+	class WhiteboardPayload;
+
+	class WhiteboardSession {
+	public:
+		typedef boost::shared_ptr<WhiteboardSession> ref;
+
+	public:
+		WhiteboardSession(const JID& jid, IQRouter* router);
+		virtual ~WhiteboardSession();
+		void handleIncomingAction(boost::shared_ptr<WhiteboardPayload> payload);
+		void sendElement(const WhiteboardElement::ref element);
+		virtual void sendOperation(WhiteboardOperation::ref operation) = 0;
+		void cancel();
+		const JID& getTo() const;
+
+	public:
+		boost::signal< void(const WhiteboardElement::ref element)> onElementReceived; 
+		boost::signal< void(const WhiteboardOperation::ref operation)> onOperationReceived;
+		boost::signal< void(const JID& contact)> onSessionTerminated;
+		boost::signal< void(const JID& contact)> onRequestAccepted;
+		boost::signal< void(const JID& contact)> onRequestRejected;
+
+	private:
+		virtual void handleIncomingOperation(WhiteboardOperation::ref operation) = 0;
+
+	protected:
+		void sendPayload(boost::shared_ptr<WhiteboardPayload> payload);
+
+		JID toJID_;
+		IQRouter* router_;
+		std::string lastOpID;
+		IDGenerator idGenerator;
+	};
+}
diff --git a/Swiften/Whiteboard/WhiteboardSessionManager.cpp b/Swiften/Whiteboard/WhiteboardSessionManager.cpp
new file mode 100644
index 0000000..c8e9a6a
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardSessionManager.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+
+#include <Swiften/Whiteboard/WhiteboardSessionManager.h>
+
+#include <Swiften/Base/foreach.h>
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/bind.hpp>
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/Whiteboard/WhiteboardResponder.h>
+#include <Swiften/Presence/PresenceOracle.h>
+#include "Swiften/Disco/EntityCapsProvider.h"
+
+namespace Swift {
+	WhiteboardSessionManager::WhiteboardSessionManager(IQRouter* router, StanzaChannel* stanzaChannel, PresenceOracle* presenceOracle, EntityCapsProvider* capsProvider) : router_(router), stanzaChannel_(stanzaChannel), presenceOracle_(presenceOracle), capsProvider_(capsProvider) {
+		responder = new WhiteboardResponder(this, router);
+		responder->start();
+		stanzaChannel_->onPresenceReceived.connect(boost::bind(&WhiteboardSessionManager::handlePresenceReceived, this, _1));
+		stanzaChannel_->onAvailableChanged.connect(boost::bind(&WhiteboardSessionManager::handleAvailableChanged, this, _1));
+	}
+
+	WhiteboardSessionManager::~WhiteboardSessionManager() {
+		responder->stop();
+		delete responder;
+	}
+
+	WhiteboardSession::ref WhiteboardSessionManager::getSession(const JID& to) {
+		if (sessions_.find(to) == sessions_.end()) {
+			return boost::shared_ptr<WhiteboardSession>();
+		}
+		return sessions_[to];
+	}
+
+	OutgoingWhiteboardSession::ref WhiteboardSessionManager::createOutgoingSession(const JID& to) {
+		JID fullJID = to;
+		if (fullJID.isBare()) {
+			fullJID = getFullJID(fullJID);
+		}
+		OutgoingWhiteboardSession::ref session = boost::make_shared<OutgoingWhiteboardSession>(fullJID, router_);
+		sessions_[fullJID] = session;
+		session->onSessionTerminated.connect(boost::bind(&WhiteboardSessionManager::deleteSessionEntry, this, _1));
+		session->onRequestRejected.connect(boost::bind(&WhiteboardSessionManager::deleteSessionEntry, this, _1));
+		return session;
+	}
+
+	WhiteboardSession::ref WhiteboardSessionManager::requestSession(const JID& to) {
+		WhiteboardSession::ref session = getSession(to);
+		if (!session) {
+			OutgoingWhiteboardSession::ref outgoingSession = createOutgoingSession(to);
+			outgoingSession->startSession();
+			return outgoingSession;
+		} else {
+			return session;
+		}
+	}
+
+	void WhiteboardSessionManager::handleIncomingSession(IncomingWhiteboardSession::ref session) {
+		sessions_[session->getTo()] = session;
+		session->onSessionTerminated.connect(boost::bind(&WhiteboardSessionManager::deleteSessionEntry, this, _1));
+		onSessionRequest(session);
+	}
+
+	JID WhiteboardSessionManager::getFullJID(const JID& bareJID) {
+		JID fullReceipientJID;
+		int priority = INT_MIN;
+	
+		//getAllPresence(bareJID) gives you all presences for the bare JID (i.e. all resources) Remko Tronçon @ 11:11
+		std::vector<Presence::ref> presences = presenceOracle_->getAllPresence(bareJID);
+
+		//iterate over them
+		foreach(Presence::ref pres, presences) {
+			if (pres->getPriority() > priority) {
+				// look up caps from the jid
+				DiscoInfo::ref info = capsProvider_->getCaps(pres->getFrom());
+				if (info && info->hasFeature(DiscoInfo::WhiteboardFeature)) {
+					priority = pres->getPriority();
+					fullReceipientJID = pres->getFrom();
+				}
+			}
+		}
+	
+		return fullReceipientJID;
+	}
+
+	void WhiteboardSessionManager::deleteSessionEntry(const JID& contact) {
+		sessions_.erase(contact);
+	}
+
+	void WhiteboardSessionManager::handlePresenceReceived(Presence::ref presence) {
+		if (!presence->isAvailable()) {
+			WhiteboardSession::ref session = getSession(presence->getFrom());
+			if (session) {
+				session->cancel();
+			}
+		}
+	}
+
+	void WhiteboardSessionManager::handleAvailableChanged(bool available) {
+		if (!available) {
+			std::map<JID, WhiteboardSession::ref> sessionsCopy = sessions_;
+			std::map<JID, WhiteboardSession::ref>::iterator it;
+			for (it = sessionsCopy.begin(); it != sessionsCopy.end(); ++it) {
+				it->second->cancel();
+			}
+		}
+	}
+}
diff --git a/Swiften/Whiteboard/WhiteboardSessionManager.h b/Swiften/Whiteboard/WhiteboardSessionManager.h
new file mode 100644
index 0000000..f696eb8
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardSessionManager.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <map>
+
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Whiteboard/IncomingWhiteboardSession.h>
+#include <Swiften/Whiteboard/OutgoingWhiteboardSession.h>
+
+namespace Swift {
+	class IQRouter;
+	class WhiteboardResponder;
+	class PresenceOracle;
+	class EntityCapsProvider;
+
+	class WhiteboardSessionManager {
+		friend class WhiteboardResponder;
+	public:
+		WhiteboardSessionManager(IQRouter* router, StanzaChannel* stanzaChannel, PresenceOracle* presenceOracle, EntityCapsProvider* capsProvider);
+		~WhiteboardSessionManager();
+
+		WhiteboardSession::ref getSession(const JID& to);
+		WhiteboardSession::ref requestSession(const JID& to);
+
+	public:
+		boost::signal< void (IncomingWhiteboardSession::ref)> onSessionRequest;
+
+	private:
+		JID getFullJID(const JID& bareJID);
+		OutgoingWhiteboardSession::ref createOutgoingSession(const JID& to);
+		void handleIncomingSession(IncomingWhiteboardSession::ref session);
+		void handlePresenceReceived(Presence::ref presence);
+		void handleAvailableChanged(bool available);
+		void deleteSessionEntry(const JID& contact);
+
+	private:
+		std::map<JID, boost::shared_ptr<WhiteboardSession> > sessions_;
+		IQRouter* router_;
+		StanzaChannel* stanzaChannel_;
+		PresenceOracle* presenceOracle_;
+		EntityCapsProvider* capsProvider_;
+		WhiteboardResponder* responder;
+	};
+}
diff --git a/Swiften/Whiteboard/WhiteboardTransformer.cpp b/Swiften/Whiteboard/WhiteboardTransformer.cpp
new file mode 100644
index 0000000..8b9c927
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardTransformer.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/WhiteboardTransformer.h>
+#include <boost/smart_ptr/make_shared.hpp>
+
+namespace Swift {
+	std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardOperation::ref clientOp, WhiteboardOperation::ref serverOp) {
+		WhiteboardInsertOperation::ref clientInsert = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(clientOp);
+		WhiteboardInsertOperation::ref serverInsert = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(serverOp);
+		WhiteboardUpdateOperation::ref clientUpdate = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(clientOp);
+		WhiteboardUpdateOperation::ref serverUpdate = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(serverOp);
+		WhiteboardDeleteOperation::ref clientDelete = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(clientOp);
+		WhiteboardDeleteOperation::ref serverDelete = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(serverOp);
+		if (clientInsert && serverInsert) {
+			return transform(clientInsert, serverInsert);
+		} else if (clientUpdate && serverUpdate) {
+			return transform(clientUpdate, serverUpdate);
+		} else if (clientInsert && serverUpdate) {
+			return transform(clientInsert, serverUpdate);
+		} else if (clientUpdate && serverInsert) {
+			return transform(clientUpdate, serverInsert);
+		} else if (clientDelete && serverDelete) {
+			return transform(clientDelete, serverDelete);
+		} else if (clientInsert && serverDelete) {
+			return transform(clientInsert, serverDelete);
+		} else if (clientDelete && serverInsert) {
+			return transform(clientDelete, serverInsert);
+		} else if (clientUpdate && serverDelete) {
+			return transform(clientUpdate, serverDelete);
+		} else if (clientDelete && serverUpdate) {
+			return transform(clientDelete, serverUpdate);
+		} else {
+			return std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref>();
+		}
+	}
+
+	std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardInsertOperation::ref clientOp, WhiteboardInsertOperation::ref serverOp) {
+		std::pair<WhiteboardInsertOperation::ref, WhiteboardInsertOperation::ref> result;
+		result.first = boost::make_shared<WhiteboardInsertOperation>(*serverOp);
+		result.first->setParentID(clientOp->getID());
+		result.second = boost::make_shared<WhiteboardInsertOperation>(*clientOp);
+		result.second->setParentID(serverOp->getID());
+		if (clientOp->getPos() <= serverOp->getPos()) {
+			result.first->setPos(result.first->getPos()+1);
+		} else {
+			result.second->setPos(result.second->getPos()+1);
+		}
+		return result;
+	}
+
+	std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardUpdateOperation::ref clientOp, WhiteboardUpdateOperation::ref serverOp) {
+		std::pair<WhiteboardUpdateOperation::ref, WhiteboardUpdateOperation::ref> result;
+		result.first = boost::make_shared<WhiteboardUpdateOperation>(*serverOp);
+		result.first->setParentID(clientOp->getID());
+
+		if (clientOp->getPos() == serverOp->getPos()) {
+			result.second = boost::make_shared<WhiteboardUpdateOperation>(*serverOp);
+			result.second->setID(clientOp->getID());
+			result.second->setParentID(serverOp->getID());
+		} else {
+			result.second = boost::make_shared<WhiteboardUpdateOperation>(*clientOp);
+			result.second->setParentID(serverOp->getID());
+		}
+
+		if (clientOp->getPos() < serverOp->getPos() && clientOp->getNewPos() > serverOp->getPos()) {
+			result.first->setPos(result.first->getPos()-1);
+			if (clientOp->getNewPos() >= serverOp->getNewPos()) {
+				result.first->setNewPos(result.first->getNewPos()-1);
+			}
+		} else if (clientOp->getNewPos() >= serverOp->getNewPos()) {
+			result.first->setNewPos(result.first->getNewPos()-1);
+		}
+
+		if (serverOp->getPos() < clientOp->getPos() && serverOp->getNewPos() > clientOp->getPos()) {
+			result.second->setPos(result.second->getPos()-1);
+			if (serverOp->getNewPos() >= clientOp->getNewPos()) {
+				result.second->setNewPos(result.second->getNewPos()-1);
+			}
+		} else if (serverOp->getNewPos() >= clientOp->getNewPos()) {
+			result.second->setNewPos(result.second->getNewPos()-1);
+		}
+		return result;
+	}
+
+	std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardUpdateOperation::ref clientOp, WhiteboardInsertOperation::ref serverOp) {
+		std::pair<WhiteboardInsertOperation::ref, WhiteboardUpdateOperation::ref> result;
+		result.first = boost::make_shared<WhiteboardInsertOperation>(*serverOp);
+		result.first->setParentID(clientOp->getID());
+		result.second = boost::make_shared<WhiteboardUpdateOperation>(*clientOp);
+		result.second->setParentID(serverOp->getID());
+		if (serverOp->getPos() <= clientOp->getPos()) {
+			result.second->setPos(result.second->getPos()+1);
+		}
+		return result;
+	}
+
+	std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardInsertOperation::ref clientOp, WhiteboardUpdateOperation::ref serverOp) {
+		std::pair<WhiteboardUpdateOperation::ref, WhiteboardInsertOperation::ref> result;
+		result.first = boost::make_shared<WhiteboardUpdateOperation>(*serverOp);
+		result.first->setParentID(clientOp->getID());
+		result.second = boost::make_shared<WhiteboardInsertOperation>(*clientOp);
+		result.second->setParentID(serverOp->getID());
+		if (serverOp->getPos() >= clientOp->getPos()) {
+			result.first->setPos(result.first->getPos()+1);
+		}
+		return result;
+	}
+
+	std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardDeleteOperation::ref clientOp, WhiteboardDeleteOperation::ref serverOp) {
+		std::pair<WhiteboardDeleteOperation::ref, WhiteboardDeleteOperation::ref> result;
+		result.first = boost::make_shared<WhiteboardDeleteOperation>(*serverOp);
+		result.first->setParentID(clientOp->getID());
+		result.second = boost::make_shared<WhiteboardDeleteOperation>(*clientOp);
+		result.second->setParentID(serverOp->getID());
+		if (clientOp->getPos() == -1) {
+			result.second->setPos(-1);
+		}
+		if (serverOp->getPos() == -1) {
+			result.first->setPos(-1);
+		}
+		if (clientOp->getPos() < serverOp->getPos()) {
+			result.first->setPos(result.first->getPos()-1);
+		} else if (clientOp->getPos() > serverOp->getPos()) {
+			result.second->setPos(result.second->getPos()-1);
+		} else {
+			result.first->setPos(-1);
+			result.second->setPos(-1);
+		}
+		return result;
+	}
+
+	std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardInsertOperation::ref clientOp, WhiteboardDeleteOperation::ref serverOp) {
+		std::pair<WhiteboardDeleteOperation::ref, WhiteboardInsertOperation::ref> result;
+		result.first = boost::make_shared<WhiteboardDeleteOperation>(*serverOp);
+		result.first->setParentID(clientOp->getID());
+		result.second = boost::make_shared<WhiteboardInsertOperation>(*clientOp);
+		result.second->setParentID(serverOp->getID());
+		if (clientOp->getPos() <= serverOp->getPos()) {
+			result.first->setPos(result.first->getPos()+1);
+		} else if (serverOp->getPos() != -1) {
+			result.second->setPos(result.second->getPos()-1);
+		}
+		return result;
+	}
+
+	std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardDeleteOperation::ref clientOp, WhiteboardInsertOperation::ref serverOp) {
+		std::pair<WhiteboardInsertOperation::ref, WhiteboardDeleteOperation::ref> result;
+		result.first = boost::make_shared<WhiteboardInsertOperation>(*serverOp);
+		result.first->setParentID(clientOp->getID());
+		result.second = boost::make_shared<WhiteboardDeleteOperation>(*clientOp);
+		result.second->setParentID(serverOp->getID());
+		if (serverOp->getPos() <= clientOp->getPos()) {
+			result.second->setPos(result.second->getPos()+1);
+		} else if (clientOp->getPos() != -1) {
+			result.first->setPos(result.first->getPos()-1);
+		}
+		return result;
+	}
+
+	std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardUpdateOperation::ref clientOp, WhiteboardDeleteOperation::ref serverOp) {
+		std::pair<WhiteboardDeleteOperation::ref, WhiteboardOperation::ref> result;
+		result.first = boost::make_shared<WhiteboardDeleteOperation>(*serverOp);
+		result.first->setParentID(clientOp->getID());
+		WhiteboardUpdateOperation::ref updateOp = boost::make_shared<WhiteboardUpdateOperation>(*clientOp);
+		result.second = updateOp;
+		result.second->setParentID(serverOp->getID());
+		if (clientOp->getPos() == serverOp->getPos()) {
+			WhiteboardDeleteOperation::ref deleteOp = boost::make_shared<WhiteboardDeleteOperation>();
+			result.second = deleteOp; 
+			result.second->setPos(-1);
+			result.second->setID(clientOp->getID());
+			result.second->setParentID(serverOp->getID());
+			deleteOp->setElementID(serverOp->getElementID());
+		} else if (clientOp->getPos() > serverOp->getPos() && clientOp->getNewPos() <= serverOp->getPos()) {
+			result.second->setPos(result.second->getPos()-1);
+		} else if (clientOp->getPos() < serverOp->getPos() && clientOp->getNewPos() >= serverOp->getPos()) {
+			updateOp->setNewPos(updateOp->getNewPos()-1);
+		} else if (clientOp->getPos() > serverOp->getPos()) {
+			result.second->setPos(result.second->getPos()-1);
+			updateOp->setNewPos(updateOp->getNewPos()-1);
+		}
+		return result;
+	}
+
+	std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardDeleteOperation::ref clientOp, WhiteboardUpdateOperation::ref serverOp) {
+		std::pair<WhiteboardOperation::ref, WhiteboardDeleteOperation::ref> result;
+		WhiteboardUpdateOperation::ref updateOp = boost::make_shared<WhiteboardUpdateOperation>(*serverOp);
+		result.first = updateOp;
+		result.first->setParentID(clientOp->getID());
+		result.second = boost::make_shared<WhiteboardDeleteOperation>(*clientOp);
+		result.second->setParentID(serverOp->getID());
+		if (clientOp->getPos() == serverOp->getPos()) {
+			WhiteboardDeleteOperation::ref deleteOp = boost::make_shared<WhiteboardDeleteOperation>();
+			result.first = deleteOp;
+			result.first->setPos(-1);
+			result.first->setID(serverOp->getID());
+			result.first->setParentID(clientOp->getID());
+			deleteOp->setElementID(clientOp->getElementID());
+		} else if (clientOp->getPos() < serverOp->getPos() && clientOp->getPos() >= serverOp->getNewPos()) {
+			result.first->setPos(result.first->getPos()-1);
+		} else if (clientOp->getPos() > serverOp->getPos() && clientOp->getPos() <= serverOp->getNewPos()) {
+			updateOp->setNewPos(updateOp->getNewPos()-1);
+		} else if (clientOp->getPos() < serverOp->getPos()) {
+			result.first->setPos(result.first->getPos()-1);
+			updateOp->setNewPos(updateOp->getNewPos()-1);
+		}
+		return result;
+	}
+}
diff --git a/Swiften/Whiteboard/WhiteboardTransformer.h b/Swiften/Whiteboard/WhiteboardTransformer.h
new file mode 100644
index 0000000..5811f9f
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardTransformer.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h>
+#include <utility>
+
+namespace Swift {
+	class WhiteboardTransformer {
+	public:
+		static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardOperation::ref clientOp, WhiteboardOperation::ref serverOp);
+		static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardInsertOperation::ref clientOp, WhiteboardInsertOperation::ref serverOp);
+		static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardUpdateOperation::ref clientOp, WhiteboardUpdateOperation::ref serverOp);
+		static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardUpdateOperation::ref clientOp, WhiteboardInsertOperation::ref serverOp);
+		static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardInsertOperation::ref clientOp, WhiteboardUpdateOperation::ref serverOp);
+		static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardDeleteOperation::ref clientOp, WhiteboardDeleteOperation::ref serverOp);
+		static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardInsertOperation::ref clientOp, WhiteboardDeleteOperation::ref serverOp);
+		static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardDeleteOperation::ref clientOp, WhiteboardInsertOperation::ref serverOp);
+		static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardUpdateOperation::ref clientOp, WhiteboardDeleteOperation::ref serverOp);
+		static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardDeleteOperation::ref clientOp, WhiteboardUpdateOperation::ref serverOp);
+	};
+}
-- 
cgit v0.10.2-6-g49f6