diff options
107 files changed, 5452 insertions, 10 deletions
| 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.pngBinary files differ new file mode 100644 index 0000000..001f4d6 --- /dev/null +++ b/Swift/resources/icons/circle.png diff --git a/Swift/resources/icons/cursor.png b/Swift/resources/icons/cursor.pngBinary files differ new file mode 100644 index 0000000..9e6a7d3 --- /dev/null +++ b/Swift/resources/icons/cursor.png diff --git a/Swift/resources/icons/eraser.png b/Swift/resources/icons/eraser.pngBinary files differ new file mode 100644 index 0000000..b9b7522 --- /dev/null +++ b/Swift/resources/icons/eraser.png diff --git a/Swift/resources/icons/handline.png b/Swift/resources/icons/handline.pngBinary files differ new file mode 100644 index 0000000..a085c14 --- /dev/null +++ b/Swift/resources/icons/handline.png diff --git a/Swift/resources/icons/line.png b/Swift/resources/icons/line.pngBinary files differ new file mode 100644 index 0000000..3b6cf31 --- /dev/null +++ b/Swift/resources/icons/line.png diff --git a/Swift/resources/icons/polygon.png b/Swift/resources/icons/polygon.pngBinary files differ new file mode 100644 index 0000000..da2af14 --- /dev/null +++ b/Swift/resources/icons/polygon.png diff --git a/Swift/resources/icons/rect.png b/Swift/resources/icons/rect.pngBinary files differ new file mode 100644 index 0000000..6bdbea2 --- /dev/null +++ b/Swift/resources/icons/rect.png diff --git a/Swift/resources/icons/text.png b/Swift/resources/icons/text.pngBinary files differ new file mode 100644 index 0000000..ddd76a0 --- /dev/null +++ b/Swift/resources/icons/text.png 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); +	}; +} | 
 Swift
 Swift