summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--BuildTools/SCons/SConscript.boot1
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp26
-rw-r--r--Swift/Controllers/Chat/ChatController.h7
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp34
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h7
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp14
-rw-r--r--Swift/Controllers/Chat/UnitTest/MockChatListWindow.h2
-rw-r--r--Swift/Controllers/MainController.cpp10
-rw-r--r--Swift/Controllers/MainController.h2
-rw-r--r--Swift/Controllers/Roster/ContactRosterItem.h1
-rw-r--r--Swift/Controllers/Roster/RosterController.cpp3
-rw-r--r--Swift/Controllers/SConscript3
-rw-r--r--Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h24
-rw-r--r--Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h24
-rw-r--r--Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h21
-rw-r--r--Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h22
-rw-r--r--Swift/Controllers/UIInterfaces/ChatListWindow.h3
-rw-r--r--Swift/Controllers/UIInterfaces/ChatWindow.h9
-rw-r--r--Swift/Controllers/UIInterfaces/UIFactory.h4
-rw-r--r--Swift/Controllers/UIInterfaces/WhiteboardWindow.h26
-rw-r--r--Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h19
-rw-r--r--Swift/Controllers/UnitTest/MockChatWindow.h4
-rw-r--r--Swift/Controllers/WhiteboardManager.cpp136
-rw-r--r--Swift/Controllers/WhiteboardManager.h59
-rw-r--r--Swift/QtUI/ChatList/ChatListDelegate.cpp22
-rw-r--r--Swift/QtUI/ChatList/ChatListDelegate.h2
-rw-r--r--Swift/QtUI/ChatList/ChatListModel.cpp22
-rw-r--r--Swift/QtUI/ChatList/ChatListModel.h3
-rw-r--r--Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp47
-rw-r--r--Swift/QtUI/ChatList/ChatListWhiteboardItem.h35
-rw-r--r--Swift/QtUI/ChatList/QtChatListWindow.cpp15
-rw-r--r--Swift/QtUI/ChatList/QtChatListWindow.h2
-rw-r--r--Swift/QtUI/QtChatView.cpp14
-rw-r--r--Swift/QtUI/QtChatView.h1
-rw-r--r--Swift/QtUI/QtChatWindow.cpp50
-rw-r--r--Swift/QtUI/QtChatWindow.h6
-rw-r--r--Swift/QtUI/QtUIFactory.cpp6
-rw-r--r--Swift/QtUI/QtUIFactory.h2
-rw-r--r--Swift/QtUI/Roster/QtRosterWidget.cpp12
-rw-r--r--Swift/QtUI/SConscript6
-rw-r--r--Swift/QtUI/Swift.qrc8
-rw-r--r--Swift/QtUI/Whiteboard/ColorWidget.cpp37
-rw-r--r--Swift/QtUI/Whiteboard/ColorWidget.h33
-rw-r--r--Swift/QtUI/Whiteboard/FreehandLineItem.cpp97
-rw-r--r--Swift/QtUI/Whiteboard/FreehandLineItem.h35
-rw-r--r--Swift/QtUI/Whiteboard/GView.cpp499
-rw-r--r--Swift/QtUI/Whiteboard/GView.h75
-rw-r--r--Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp389
-rw-r--r--Swift/QtUI/Whiteboard/QtWhiteboardWindow.h88
-rw-r--r--Swift/QtUI/Whiteboard/TextDialog.cpp52
-rw-r--r--Swift/QtUI/Whiteboard/TextDialog.h45
-rw-r--r--Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h187
-rw-r--r--Swift/resources/icons/circle.pngbin0 -> 223 bytes
-rw-r--r--Swift/resources/icons/cursor.pngbin0 -> 382 bytes
-rw-r--r--Swift/resources/icons/eraser.pngbin0 -> 774 bytes
-rw-r--r--Swift/resources/icons/handline.pngbin0 -> 394 bytes
-rw-r--r--Swift/resources/icons/line.pngbin0 -> 170 bytes
-rw-r--r--Swift/resources/icons/polygon.pngbin0 -> 451 bytes
-rw-r--r--Swift/resources/icons/rect.pngbin0 -> 187 bytes
-rw-r--r--Swift/resources/icons/text.pngbin0 -> 229 bytes
-rw-r--r--Swiften/Base/String.cpp18
-rw-r--r--Swiften/Base/String.h3
-rw-r--r--Swiften/Client/Client.cpp12
-rw-r--r--Swiften/Client/Client.h4
-rw-r--r--Swiften/Elements/DiscoInfo.cpp1
-rw-r--r--Swiften/Elements/DiscoInfo.h1
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardColor.cpp55
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardColor.h28
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h32
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardElement.h32
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h27
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h74
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h57
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h32
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardLineElement.h65
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardOperation.h48
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h63
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardRectElement.h74
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardTextElement.h64
-rw-r--r--Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h41
-rw-r--r--Swiften/Elements/WhiteboardPayload.h64
-rw-r--r--Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp2
-rw-r--r--Swiften/Parser/PayloadParsers/WhiteboardParser.cpp315
-rw-r--r--Swiften/Parser/PayloadParsers/WhiteboardParser.h34
-rw-r--r--Swiften/Parser/SConscript1
-rw-r--r--Swiften/SConscript12
-rw-r--r--Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp2
-rw-r--r--Swiften/Serializer/PayloadSerializers/WhiteboardSerializer.cpp205
-rw-r--r--Swiften/Serializer/PayloadSerializers/WhiteboardSerializer.h45
-rw-r--r--Swiften/Whiteboard/IncomingWhiteboardSession.cpp58
-rw-r--r--Swiften/Whiteboard/IncomingWhiteboardSession.h30
-rw-r--r--Swiften/Whiteboard/OutgoingWhiteboardSession.cpp59
-rw-r--r--Swiften/Whiteboard/OutgoingWhiteboardSession.h31
-rw-r--r--Swiften/Whiteboard/UnitTest/WhiteboardClientTest.cpp676
-rw-r--r--Swiften/Whiteboard/UnitTest/WhiteboardServerTest.cpp128
-rw-r--r--Swiften/Whiteboard/WhiteboardClient.cpp120
-rw-r--r--Swiften/Whiteboard/WhiteboardClient.h36
-rw-r--r--Swiften/Whiteboard/WhiteboardResponder.cpp37
-rw-r--r--Swiften/Whiteboard/WhiteboardResponder.h25
-rw-r--r--Swiften/Whiteboard/WhiteboardServer.cpp48
-rw-r--r--Swiften/Whiteboard/WhiteboardServer.h23
-rw-r--r--Swiften/Whiteboard/WhiteboardSession.cpp67
-rw-r--r--Swiften/Whiteboard/WhiteboardSession.h54
-rw-r--r--Swiften/Whiteboard/WhiteboardSessionManager.cpp111
-rw-r--r--Swiften/Whiteboard/WhiteboardSessionManager.h53
-rw-r--r--Swiften/Whiteboard/WhiteboardTransformer.cpp213
-rw-r--r--Swiften/Whiteboard/WhiteboardTransformer.h28
107 files changed, 5451 insertions, 8 deletions
diff --git a/BuildTools/SCons/SConscript.boot b/BuildTools/SCons/SConscript.boot
index d6977fe..2072239 100644
--- a/BuildTools/SCons/SConscript.boot
+++ b/BuildTools/SCons/SConscript.boot
@@ -181,6 +181,7 @@ if not env["assertions"] :
if env["experimental"] :
env.Append(CPPDEFINES = ["SWIFT_EXPERIMENTAL_FT"])
+ env.Append(CPPDEFINES = ["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 2fa4559..4f95cf7 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 7043231..6687c70 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -29,6 +29,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:
@@ -55,6 +57,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();
@@ -76,6 +82,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 4fc3752..d9c18b8 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 {
@@ -71,7 +72,8 @@ ChatsManager::ChatsManager(
FileTransferOverview* ftOverview,
XMPPRoster* roster,
bool eagleMode,
- SettingsProvider* settings) :
+ SettingsProvider* settings,
+ WhiteboardManager* whiteboardManager) :
jid_(jid),
joinMUCWindowFactory_(joinMUCWindowFactory),
useDelayForLatency_(useDelayForLatency),
@@ -81,7 +83,8 @@ ChatsManager::ChatsManager(
ftOverview_(ftOverview),
roster_(roster),
eagleMode_(eagleMode),
- settings_(settings) {
+ settings_(settings),
+ whiteboardManager_(whiteboardManager) {
timerFactory_ = timerFactory;
eventController_ = eventController;
stanzaChannel_ = stanzaChannel;
@@ -107,6 +110,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));
@@ -655,6 +662,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 a8c69c4..97f0937 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,10 +48,11 @@ namespace Swift {
class FileTransferController;
class XMPPRoster;
class SettingsProvider;
+ class WhiteboardManager;
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);
+ 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, WhiteboardManager* whiteboardManager);
virtual ~ChatsManager();
void setAvatarManager(AvatarManager* avatarManager);
void setOnline(bool enabled);
@@ -72,6 +74,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();
@@ -129,5 +133,6 @@ namespace Swift {
bool eagleMode_;
bool userWantsReceipts_;
SettingsProvider* settings_;
+ WhiteboardManager* whiteboardManager_;
};
}
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index 11d0ce2..84e4c03 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_);
+ 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_, 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 f124298..6154a41 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -42,6 +42,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"
@@ -240,6 +241,8 @@ void MainController::resetClient() {
userSearchControllerAdd_ = NULL;
delete adHocManager_;
adHocManager_ = NULL;
+ delete whiteboardManager_;
+ whiteboardManager_ = NULL;
clientInitialized_ = false;
}
@@ -289,13 +292,14 @@ 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
* be before they receive stanzas that need it (e.g. bookmarks).*/
client_->getVCardManager()->requestOwnVCard();
- 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_);
+ 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_, whiteboardManager_);
client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1));
chatsManager_->setAvatarManager(client_->getAvatarManager());
@@ -315,6 +319,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);
@@ -322,6 +329,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 eeba9f3..4395c67 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -68,6 +68,7 @@ namespace Swift {
class AdHocManager;
class AdHocCommandWindowFactory;
class FileTransferOverview;
+ class WhiteboardManager;
class MainController {
public:
@@ -167,5 +168,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 eca0d38..467b114 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -71,7 +71,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 cf89dab..b583bf0 100644
--- a/Swift/Controllers/UIInterfaces/UIFactory.h
+++ b/Swift/Controllers/UIInterfaces/UIFactory.h
@@ -19,6 +19,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 :
@@ -34,7 +35,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 49e5974..24d392a 100644
--- a/Swift/QtUI/QtChatView.cpp
+++ b/Swift/QtUI/QtChatView.cpp
@@ -376,6 +376,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 fdbdd5a..6aec5b0 100644
--- a/Swift/QtUI/QtChatView.h
+++ b/Swift/QtUI/QtChatView.h
@@ -42,6 +42,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);
signals:
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index f42469b..16adb87 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 78de7aa..82f4bb4 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -25,8 +25,10 @@
#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 <Swiften/Whiteboard/WhiteboardSession.h>
namespace Swift {
@@ -134,6 +136,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 edb89ad..5f23c32 100644
--- a/Swift/QtUI/QtUIFactory.h
+++ b/Swift/QtUI/QtUIFactory.h
@@ -24,6 +24,7 @@ namespace Swift {
class QtChatWindowFactory;
class QtChatWindow;
class TimerFactory;
+ class WhiteboardSession;
class QtUIFactory : public QObject, public UIFactory {
Q_OBJECT
@@ -42,6 +43,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 064faab..6095b6c 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -130,6 +130,7 @@ sources = [
"ChatList/ChatListDelegate.cpp",
"ChatList/ChatListMUCItem.cpp",
"ChatList/ChatListRecentItem.cpp",
+ "ChatList/ChatListWhiteboardItem.cpp",
"MUCSearch/QtMUCSearchWindow.cpp",
"MUCSearch/MUCSearchModel.cpp",
"MUCSearch/MUCSearchRoomItem.cpp",
@@ -142,6 +143,11 @@ sources = [
"UserSearch/QtUserSearchWindow.cpp",
"UserSearch/UserSearchModel.cpp",
"UserSearch/UserSearchDelegate.cpp",
+ "Whiteboard/FreehandLineItem.cpp",
+ "Whiteboard/GView.cpp",
+ "Whiteboard/TextDialog.cpp",
+ "Whiteboard/QtWhiteboardWindow.cpp",
+ "Whiteboard/ColorWidget.cpp",
"QtSubscriptionRequestWindow.cpp",
"QtRosterHeader.cpp",
"QtWebView.cpp",
diff --git a/Swift/QtUI/Swift.qrc b/Swift/QtUI/Swift.qrc
index cf1ee05..eb4f7ee 100644
--- a/Swift/QtUI/Swift.qrc
+++ b/Swift/QtUI/Swift.qrc
@@ -21,5 +21,13 @@
<file alias="icons/new-chat.png">../resources/icons/new-chat.png</file>
<file alias="icons/actions.png">../resources/icons/actions.png</file>
<file alias="COPYING">COPYING</file>
+ <file alias="icons/line.png">../resources/icons/line.png</file>
+ <file alias="icons/rect.png">../resources/icons/rect.png</file>
+ <file alias="icons/circle.png">../resources/icons/circle.png</file>
+ <file alias="icons/handline.png">../resources/icons/handline.png</file>
+ <file alias="icons/text.png">../resources/icons/text.png</file>
+ <file alias="icons/polygon.png">../resources/icons/polygon.png</file>
+ <file alias="icons/cursor.png">../resources/icons/cursor.png</file>
+ <file alias="icons/eraser.png">../resources/icons/eraser.png</file>
</qresource>
</RCC>
diff --git a/Swift/QtUI/Whiteboard/ColorWidget.cpp b/Swift/QtUI/Whiteboard/ColorWidget.cpp
new file mode 100644
index 0000000..e96b760
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/ColorWidget.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+
+#include "ColorWidget.h"
+#include <QPainter>
+#include <QMouseEvent>
+
+namespace Swift {
+ ColorWidget::ColorWidget(QWidget* parent) : QWidget(parent) {
+ setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
+ }
+
+ QSize ColorWidget::sizeHint() const {
+ return QSize(20, 20);
+ }
+
+ void ColorWidget::setColor(QColor color) {
+ this->color = color;
+ update();
+ }
+
+ void ColorWidget::paintEvent(QPaintEvent* /*event*/) {
+ QPainter painter(this);
+ painter.fillRect(0, 0, 20, 20, color);
+ }
+
+ void ColorWidget::mouseReleaseEvent(QMouseEvent* event) {
+ if (event->button() == Qt::LeftButton) {
+ emit clicked();
+ }
+ }
+}
+
diff --git a/Swift/QtUI/Whiteboard/ColorWidget.h b/Swift/QtUI/Whiteboard/ColorWidget.h
new file mode 100644
index 0000000..6abdf00
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/ColorWidget.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QWidget>
+
+namespace Swift {
+ class ColorWidget : public QWidget {
+ Q_OBJECT;
+ public:
+ ColorWidget(QWidget* parent = 0);
+ QSize sizeHint() const;
+
+ public slots:
+ void setColor(QColor color);
+
+ private:
+ QColor color;
+
+ protected:
+ void paintEvent(QPaintEvent* /*event*/);
+ void mouseReleaseEvent(QMouseEvent* event);
+
+ signals:
+ void clicked();
+
+ };
+}
+
diff --git a/Swift/QtUI/Whiteboard/FreehandLineItem.cpp b/Swift/QtUI/Whiteboard/FreehandLineItem.cpp
new file mode 100644
index 0000000..8821062
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/FreehandLineItem.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "FreehandLineItem.h"
+
+
+namespace Swift {
+ FreehandLineItem::FreehandLineItem(QGraphicsItem* parent) : QGraphicsItem(parent) {
+ }
+
+ QRectF FreehandLineItem::boundingRect() const
+ {
+ return boundRect;
+ }
+
+ void FreehandLineItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/)
+ {
+ painter->setPen(pen_);
+ if (points_.size() > 0) {
+ QVector<QPointF>::const_iterator it = points_.begin();
+ QPointF previous = *it;
+ ++it;
+ for (; it != points_.end(); ++it) {
+ painter->drawLine(previous, *it);
+ previous = *it;
+ }
+ }
+ }
+
+ void FreehandLineItem::setStartPoint(QPointF point)
+ {
+ points_.clear();
+ points_.append(point);
+ QRectF rect(point, point);
+ prepareGeometryChange();
+ boundRect = rect;
+ }
+
+ void FreehandLineItem::lineTo(QPointF point)
+ {
+ qreal x1, x2, y1, y2;
+ x1 = points_.last().x();
+ x2 = point.x();
+ y1 = points_.last().y();
+ y2 = point.y();
+ if (x1 > x2) {
+ qreal temp = x1;
+ x1 = x2;
+ x2 = temp;
+ }
+ if (y1 > y2) {
+ qreal temp = y1;
+ y1 = y2;
+ y2 = temp;
+ }
+ QRectF rect(x1-1, y1-1, x2+1-x1, y2+1-y1);
+
+ points_.append(point);
+
+ prepareGeometryChange();
+ boundRect |= rect;
+ }
+
+ bool FreehandLineItem::collidesWithPath(const QPainterPath& path, Qt::ItemSelectionMode /*mode*/) const
+ {
+ QVector<QPointF>::const_iterator it;
+ QSizeF size(1,1);
+ for (it = points_.begin(); it != points_.end(); ++it) {
+ if (path.intersects(QRectF(*it, size))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void FreehandLineItem::setPen(const QPen& pen)
+ {
+ pen_ = pen;
+ update(boundRect);
+ }
+
+ QPen FreehandLineItem::pen() const
+ {
+ return pen_;
+ }
+
+ const QVector<QPointF>& FreehandLineItem::points() const {
+ return points_;
+ }
+
+ int FreehandLineItem::type() const {
+ return Type;
+ }
+}
diff --git a/Swift/QtUI/Whiteboard/FreehandLineItem.h b/Swift/QtUI/Whiteboard/FreehandLineItem.h
new file mode 100644
index 0000000..f3c6607
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/FreehandLineItem.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QGraphicsItem>
+#include <QPainter>
+#include <iostream>
+
+using namespace std;
+
+namespace Swift {
+ class FreehandLineItem : public QGraphicsItem {
+ public:
+ enum {Type = UserType + 1};
+ FreehandLineItem(QGraphicsItem* parent = 0);
+ QRectF boundingRect() const;
+ void paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/ = 0);
+ void setStartPoint(QPointF point);
+ void lineTo(QPointF point);
+ bool collidesWithPath(const QPainterPath& path, Qt::ItemSelectionMode /*mode*/ = Qt::IntersectsItemShape) const;
+ void setPen(const QPen& pen);
+ QPen pen() const;
+ const QVector<QPointF>& points() const;
+ int type() const;
+
+ private:
+ QPen pen_;
+ QVector<QPointF> points_;
+ QRectF boundRect;
+ };
+}
diff --git a/Swift/QtUI/Whiteboard/GView.cpp b/Swift/QtUI/Whiteboard/GView.cpp
new file mode 100644
index 0000000..d725cbb
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/GView.cpp
@@ -0,0 +1,499 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "GView.h"
+#include <QtSwiftUtil.h>
+
+namespace Swift {
+ GView::GView(QGraphicsScene* scene, QWidget* parent) : QGraphicsView(scene, parent), brush(QColor(Qt::white)), defaultBrush(QColor(Qt::white)) {
+ selectionRect = 0;
+ lastItem = 0;
+ zValue = 0;
+ }
+
+ void GView::setLineWidth(int i) {
+ pen.setWidth(i);
+ if (selectionRect) {
+ QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+ changePenAndBrush(selectionRect->data(1).value<QGraphicsItem*>(), pen, brush);
+ lastItemChanged(item, items_.indexOf(item)+1, Update);
+ } else {
+ defaultPen.setWidth(i);
+ }
+ }
+
+ void GView::setLineColor(QColor color) {
+ pen.setColor(color);
+ if (selectionRect) {
+ QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+ changePenAndBrush(selectionRect->data(1).value<QGraphicsItem*>(), pen, brush);
+ lastItemChanged(item, items_.indexOf(item)+1, Update);
+ } else {
+ defaultPen.setColor(color);
+ }
+ lineColorChanged(color);
+ }
+
+ QColor GView::getLineColor() {
+ return pen.color();
+ }
+
+ void GView::setBrushColor(QColor color) {
+ brush.setColor(color);
+ if (selectionRect) {
+ QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+ changePenAndBrush(selectionRect->data(1).value<QGraphicsItem*>(), pen, brush);
+ lastItemChanged(item, items_.indexOf(item)+1, Update);
+ } else {
+ defaultBrush.setColor(color);
+ }
+ brushColorChanged(color);
+ }
+
+ QColor GView::getBrushColor() {
+ return brush.color();
+ }
+
+ void GView::setMode(Mode mode) {
+ this->mode = mode;
+ lastItem = 0;
+ deselect();
+ }
+
+ void GView::addItem(QGraphicsItem* item, QString id, int pos) {
+ itemsMap_.insert(id, item);
+ if (pos > items_.size()) {
+ item->setZValue(zValue++);
+ scene()->addItem(item);
+ items_.append(item);
+ } else {
+ QGraphicsItem* temp = items_.at(pos-1);
+ item->setZValue(temp->zValue());
+ scene()->addItem(item);
+ item->stackBefore(temp);
+ items_.insert(pos-1, item);
+ }
+ }
+
+ void GView::clear() {
+ scene()->clear();
+ items_.clear();
+ itemsMap_.clear();
+ lastItem = 0;
+ selectionRect = 0;
+ brush = QBrush(QColor(Qt::white));
+ defaultBrush = QBrush(QColor(Qt::white));
+ pen = QPen();
+ pen.setWidth(1);
+ defaultPen = pen;
+ lineWidthChanged(1);
+ lineColorChanged(pen.color());
+ brushColorChanged(brush.color());
+ }
+
+ QGraphicsItem* GView::getItem(QString id) {
+ return itemsMap_.value(id);
+ }
+
+ void GView::deleteItem(QString id) {
+ deselect(id);
+ QGraphicsItem* item = itemsMap_.value(id);
+ items_.removeOne(item);
+ itemsMap_.remove(id);
+ scene()->removeItem(item);
+ delete item;
+ }
+
+ QString GView::getNewID() {
+ return P2QSTRING(idGenerator.generateID());
+ }
+
+ void GView::mouseMoveEvent(QMouseEvent* event)
+ {
+ if (!mousePressed) {
+ return;
+ }
+
+ if (mode == Line) {
+ QGraphicsLineItem* item = qgraphicsitem_cast<QGraphicsLineItem*>(lastItem);
+ if(item != 0) {
+ QLineF line = item->line();
+ line.setP1(this->mapToScene(event->pos()));
+ item->setLine(line);
+
+ }
+ }
+ else if (mode == Rect) {
+ QGraphicsRectItem* item = qgraphicsitem_cast<QGraphicsRectItem*>(lastItem);
+ if (item != 0) {
+ QPointF beginPoint = item->data(0).toPointF();
+ QPointF newPoint = this->mapToScene(event->pos());
+ QRectF rect = item->rect();
+ if (beginPoint.x() <= newPoint.x() && beginPoint.y() <= newPoint.y()) {
+ rect.setTopLeft(beginPoint);
+ rect.setBottomRight(newPoint);
+ }
+ else if (beginPoint.x() > newPoint.x() && beginPoint.y() <= newPoint.y()) {
+ rect.setTopRight(beginPoint);
+ rect.setBottomLeft(newPoint);
+ }
+ else if (beginPoint.x() <= newPoint.x() && beginPoint.y() > newPoint.y()) {
+ rect.setBottomLeft(beginPoint);
+ rect.setTopRight(newPoint);
+ }
+ else if (beginPoint.x() > newPoint.x() && beginPoint.y() > newPoint.y()) {
+ rect.setBottomRight(beginPoint);
+ rect.setTopLeft(newPoint);
+ }
+ item->setRect(rect);
+ }
+ }
+ else if (mode == Circle) {
+ QGraphicsEllipseItem* item = qgraphicsitem_cast<QGraphicsEllipseItem*>(lastItem);
+ QPainterPath path;
+ QPointF beginPoint = item->data(0).toPointF();
+ QPointF newPoint = this->mapToScene(event->pos());
+ QRectF rect = item->rect();
+ if (beginPoint.x() <= newPoint.x() && beginPoint.y() <= newPoint.y()) {
+ rect.setTopLeft(beginPoint);
+ rect.setBottomRight(newPoint);
+ }
+ else if (beginPoint.x() > newPoint.x() && beginPoint.y() <= newPoint.y()) {
+ rect.setTopRight(beginPoint);
+ rect.setBottomLeft(newPoint);
+ }
+ else if (beginPoint.x() <= newPoint.x() && beginPoint.y() > newPoint.y()) {
+ rect.setBottomLeft(beginPoint);
+ rect.setTopRight(newPoint);
+ }
+ else if (beginPoint.x() > newPoint.x() && beginPoint.y() > newPoint.y()) {
+ rect.setBottomRight(beginPoint);
+ rect.setTopLeft(newPoint);
+ }
+
+ item->setRect(rect);
+ }
+ else if (mode == HandLine) {
+ FreehandLineItem* item = qgraphicsitem_cast<FreehandLineItem*>(lastItem);
+ if (item != 0) {
+ QPointF newPoint = this->mapToScene(event->pos());
+ item->lineTo(newPoint);
+ }
+ }
+ else if (mode == Polygon) {
+ QGraphicsPolygonItem* item = qgraphicsitem_cast<QGraphicsPolygonItem*>(lastItem);
+ QPointF newPoint = this->mapToScene(event->pos());
+ QPolygonF polygon = item->polygon();
+ polygon.erase(polygon.end()-1);
+ polygon.append(newPoint);
+ item->setPolygon(polygon);
+ }
+ else if (mode == Select) {
+ QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+ if (item != 0) {
+ QPainterPath path;
+ QPointF beginPoint = selectionRect->data(0).toPointF();
+ QPointF newPoint = this->mapToScene(event->pos());
+ item->setPos(beginPoint + newPoint);
+ selectionRect->setPos(beginPoint + newPoint);
+ }
+ }
+ }
+
+ void GView::mousePressEvent(QMouseEvent *event)
+ {
+ mousePressed = true;
+ deselect();
+ if (mode == Line) {
+ QPointF point = this->mapToScene(event->pos());
+ QGraphicsItem* item = scene()->addLine(point.x(), point.y(), point.x(), point.y(), pen);
+ QString id = getNewID();
+ item->setZValue(10000000);
+ item->setData(100, id);
+ item->setData(101, items_.size());
+ lastItem = item;
+ }
+ else if (mode == Rect) {
+ QPointF point = this->mapToScene(event->pos());
+ QGraphicsRectItem* item = scene()->addRect(point.x(), point.y(), 0, 0, pen, brush);
+ QString id = getNewID();
+ item->setZValue(10000000);
+ item->setData(0, point);
+ item->setData(100, id);
+ item->setData(101, items_.size());
+ lastItem = item;
+ }
+ else if (mode == Rubber) {
+ QPointF point = this->mapToScene(event->pos());
+ int w = pen.width();
+ QRectF rect(point.x()-w, point.y()-w, w*2, w*2);
+ QList<QGraphicsItem*> list = scene()->items(rect);
+ if (!list.isEmpty())
+ {
+ QGraphicsItem* item = scene()->items(rect).first();
+ QString id = item->data(100).toString();
+ int pos = items_.indexOf(item)+1;
+ itemDeleted(id, pos);
+ deleteItem(id);
+ }
+ }
+ else if (mode == Circle) {
+ QPointF point = this->mapToScene(event->pos());
+ QGraphicsEllipseItem* item = scene()->addEllipse(point.x(), point.y(), 0, 0, pen, brush);
+ QString id = getNewID();
+ item->setZValue(10000000);
+ item->setData(0, point);
+ item->setData(100, id);
+ item->setData(101, items_.size());
+ lastItem = item;
+ }
+ else if (mode == HandLine) {
+ QPointF point = this->mapToScene(event->pos());
+ FreehandLineItem* item = new FreehandLineItem;
+ QString id = getNewID();
+ item->setPen(pen);
+ item->setStartPoint(point);
+ item->setZValue(10000000);
+ item->setData(100, id);
+ item->setData(101, items_.size());
+ scene()->addItem(item);
+ lastItem = item;
+ }
+ else if (mode == Text) {
+ QPointF point = this->mapToScene(event->pos());
+ QGraphicsTextItem* item = scene()->addText("");
+ QString id = getNewID();
+ item->setData(100, id);
+ item->setData(101, items_.size());
+ item->setDefaultTextColor(pen.color());
+ textDialog = new TextDialog(item, this);
+ connect(textDialog, SIGNAL(accepted(QGraphicsTextItem*)), this, SLOT(handleTextItemModified(QGraphicsTextItem*)));
+ textDialog->setAttribute(Qt::WA_DeleteOnClose);
+ textDialog->show();
+ item->setPos(point);
+ lastItem = item;
+ }
+ else if (mode == Polygon) {
+ QPointF point = this->mapToScene(event->pos());
+ QGraphicsPolygonItem* item = dynamic_cast<QGraphicsPolygonItem*>(lastItem);
+ if (item == 0) {
+ QPolygonF polygon;
+ polygon.append(point);
+ polygon.append(point);
+ item = scene()->addPolygon(polygon, pen, brush);
+ QString id = getNewID();
+ item->setZValue(10000000);
+ item->setData(100, id);
+ item->setData(101, items_.size());
+ lastItem = item;
+ }
+ else {
+ QPolygonF polygon;
+ polygon = item->polygon();
+ polygon.append(point);
+ item->setPolygon(polygon);
+ }
+ }
+ else if (mode == Select) {
+ QPointF point = this->mapToScene(event->pos());
+ int w = pen.width();
+ if (w == 0) {
+ w = 1;
+ }
+ QRectF rect(point.x()-w, point.y()-w, w*2, w*2);
+ QList<QGraphicsItem*> list = scene()->items(rect);
+ if (!list.isEmpty()) {
+ QPen pen;
+ pen.setColor(QColor(Qt::gray));
+ pen.setStyle(Qt::DashLine);
+ QGraphicsItem *item = scene()->items(rect).first();
+ selectionRect = scene()->addRect(item->boundingRect(), pen);
+ selectionRect->setZValue(1000000);
+ selectionRect->setData(0, item->pos()-point);
+ selectionRect->setPos(item->pos());
+ QVariant var(QVariant::UserType);
+ var.setValue(item);
+ selectionRect->setData(1, var);
+ setActualPenAndBrushFromItem(item);
+ }
+ }
+ }
+
+ void GView::mouseReleaseEvent(QMouseEvent* /*event*/)
+ {
+ mousePressed = false;
+ QGraphicsPolygonItem* polygon = dynamic_cast<QGraphicsPolygonItem*>(lastItem);
+ if (polygon && polygon->polygon().size() >= 3) {
+ lastItemChanged(polygon, items_.indexOf(polygon)+1, Update);
+ } else if (lastItem) {
+ zValue++;
+ lastItem->setZValue(zValue++);
+ items_.append(lastItem);
+ itemsMap_.insert(lastItem->data(100).toString(), lastItem);
+
+ lastItemChanged(lastItem, items_.size(), New);
+ } else if (selectionRect){
+ QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+ lastItemChanged(item, items_.indexOf(item)+1, Update);
+ }
+ }
+
+
+ void GView::handleTextItemModified(QGraphicsTextItem* item) {
+ lastItemChanged(item, item->data(101).toInt(), Update);
+ }
+
+ void GView::moveUpSelectedItem()
+ {
+ if (selectionRect) {
+ QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+ int pos = items_.indexOf(item);
+ if (pos < items_.size()-1) {
+ lastItemChanged(item, pos+1, MoveUp);
+ move(item, pos+2);
+ }
+ }
+ }
+
+ void GView::moveDownSelectedItem()
+ {
+ if (selectionRect) {
+ QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
+ int pos = items_.indexOf(item);
+ if (pos > 0) {
+ lastItemChanged(item, pos+1, MoveDown);
+ move(item, pos);
+ }
+ }
+ }
+
+ void GView::move(QGraphicsItem* item, int npos) {
+ int pos = items_.indexOf(item);
+ QGraphicsItem* itemAfter = NULL;
+ if (npos-1 > pos) {
+ if (npos == items_.size()) {
+ item->setZValue(zValue++);
+ } else {
+ itemAfter = items_.at(npos);
+ }
+
+ items_.insert(npos, item);
+ items_.removeAt(pos);
+ } else if (npos-1 < pos) {
+ itemAfter = items_.at(npos-1);
+ items_.insert(npos-1, item);
+ items_.removeAt(pos+1);
+ }
+ if (itemAfter) {
+ item->setZValue(itemAfter->zValue());
+ item->stackBefore(itemAfter);
+ }
+ }
+
+ void GView::changePenAndBrush(QGraphicsItem* item, QPen pen, QBrush brush) {
+ QGraphicsLineItem* lineItem = qgraphicsitem_cast<QGraphicsLineItem*>(item);
+ if (lineItem) {
+ lineItem->setPen(pen);
+ }
+
+ FreehandLineItem* handLineItem = qgraphicsitem_cast<FreehandLineItem*>(item);
+ if (handLineItem) {
+ handLineItem->setPen(pen);
+ }
+
+ QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item);
+ if (rectItem) {
+ rectItem->setPen(pen);
+ rectItem->setBrush(brush);
+ }
+
+ QGraphicsTextItem* textItem = qgraphicsitem_cast<QGraphicsTextItem*>(item);
+ if (textItem) {
+ textItem->setDefaultTextColor(pen.color());
+ }
+
+ QGraphicsPolygonItem* polygonItem = qgraphicsitem_cast<QGraphicsPolygonItem*>(item);
+ if (polygonItem) {
+ polygonItem->setPen(pen);
+ polygonItem->setBrush(brush);
+ }
+
+ QGraphicsEllipseItem* ellipseItem = qgraphicsitem_cast<QGraphicsEllipseItem*>(item);
+ if (ellipseItem) {
+ ellipseItem->setPen(pen);
+ ellipseItem->setBrush(brush);
+ }
+ lineColorChanged(pen.color());
+ brushColorChanged(brush.color());
+ }
+
+ void GView::setActualPenAndBrushFromItem(QGraphicsItem* item) {
+ QGraphicsLineItem* lineItem = qgraphicsitem_cast<QGraphicsLineItem*>(item);
+ if (lineItem) {
+ pen = lineItem->pen();
+ }
+
+ FreehandLineItem* handLineItem = qgraphicsitem_cast<FreehandLineItem*>(item);
+ if (handLineItem) {
+ pen = handLineItem->pen();
+ }
+
+ QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item);
+ if (rectItem) {
+ pen = rectItem->pen();
+ brush = rectItem->brush();
+ }
+
+ QGraphicsTextItem* textItem = qgraphicsitem_cast<QGraphicsTextItem*>(item);
+ if (textItem) {
+ pen.setColor(textItem->defaultTextColor());
+ }
+
+ QGraphicsPolygonItem* polygonItem = qgraphicsitem_cast<QGraphicsPolygonItem*>(item);
+ if (polygonItem) {
+ pen = polygonItem->pen();
+ brush = polygonItem->brush();
+ }
+
+ QGraphicsEllipseItem* ellipseItem = qgraphicsitem_cast<QGraphicsEllipseItem*>(item);
+ if (ellipseItem) {
+ pen = ellipseItem->pen();
+ brush = ellipseItem->brush();
+ }
+ lineWidthChanged(pen.width());
+ lineColorChanged(pen.color());
+ brushColorChanged(brush.color());
+ }
+
+ void GView::deselect() {
+ if (selectionRect != 0) {
+ pen = defaultPen;
+ brush = defaultBrush;
+ scene()->removeItem(selectionRect);
+ delete selectionRect;
+ selectionRect = 0;
+ lineWidthChanged(pen.width());
+ lineColorChanged(pen.color());
+ brushColorChanged(brush.color());
+ }
+ }
+
+ void GView::deselect(QString id) {
+ if (selectionRect != 0) {
+ QGraphicsItem* item = getItem(id);
+ if (item && selectionRect->data(1).value<QGraphicsItem*>() == item) {
+ pen = defaultPen;
+ brush = defaultBrush;
+ scene()->removeItem(selectionRect);
+ delete selectionRect;
+ selectionRect = 0;
+ lineWidthChanged(pen.width());
+ lineColorChanged(pen.color());
+ brushColorChanged(brush.color());
+ }
+ }
+ }
+}
diff --git a/Swift/QtUI/Whiteboard/GView.h b/Swift/QtUI/Whiteboard/GView.h
new file mode 100644
index 0000000..88ea326
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/GView.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QGraphicsView>
+#include <QGraphicsLineItem>
+#include <QMouseEvent>
+#include <QPen>
+#include <iostream>
+#include <Swiften/Base/IDGenerator.h>
+
+#include "TextDialog.h"
+#include "FreehandLineItem.h"
+
+namespace Swift {
+ class GView : public QGraphicsView {
+ Q_OBJECT;
+ public:
+ enum Mode { Rubber, Line, Rect, Circle, HandLine, Text, Polygon, Select };
+ enum Type { New, Update, MoveUp, MoveDown };
+ GView(QGraphicsScene* scene, QWidget* parent = 0);
+ void setLineWidth(int i);
+ void setLineColor(QColor color);
+ QColor getLineColor();
+ void setBrushColor(QColor color);
+ QColor getBrushColor();
+ void setMode(Mode mode);
+ void mouseMoveEvent(QMouseEvent* event);
+ void mousePressEvent(QMouseEvent* event);
+ void mouseReleaseEvent(QMouseEvent* /*event*/);
+ void addItem(QGraphicsItem* item, QString id, int pos);
+ void clear();
+ QGraphicsItem* getItem(QString id);
+ void deleteItem(QString id);
+ QString getNewID();
+ void move(QGraphicsItem* item, int npos);
+ void deselect(QString id);
+
+ public slots:
+ void moveUpSelectedItem();
+ void moveDownSelectedItem();
+
+ private slots:
+ void handleTextItemModified(QGraphicsTextItem*);
+ private:
+ void changePenAndBrush(QGraphicsItem* item, QPen pen, QBrush brush);
+ void setActualPenAndBrushFromItem(QGraphicsItem* item);
+ void deselect();
+
+ int zValue;
+ bool mousePressed;
+ QPen pen;
+ QBrush brush;
+ QPen defaultPen;
+ QBrush defaultBrush;
+ Mode mode;
+ QGraphicsItem* lastItem;
+ QGraphicsRectItem* selectionRect;
+ TextDialog* textDialog;
+ QMap<QString, QGraphicsItem*> itemsMap_;
+ QList<QGraphicsItem*> items_;
+ IDGenerator idGenerator;
+
+ signals:
+ void lastItemChanged(QGraphicsItem* item, int pos, GView::Type type);
+ void itemDeleted(QString id, int pos);
+ void lineWidthChanged(int i);
+ void lineColorChanged(QColor color);
+ void brushColorChanged(QColor color);
+ };
+}
diff --git a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp
new file mode 100644
index 0000000..50d7f54
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "QtWhiteboardWindow.h"
+
+#include <iostream>
+
+#include <boost/bind.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Elements/WhiteboardPayload.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardLineElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardRectElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h>
+#include <Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h>
+
+#include <QMessageBox>
+#include <QLabel>
+
+namespace Swift {
+ QtWhiteboardWindow::QtWhiteboardWindow(WhiteboardSession::ref whiteboardSession) : QWidget() {
+#ifndef Q_WS_MAC
+ setWindowIcon(QIcon(":/logo-icon-16.png"));
+#endif
+ layout = new QVBoxLayout(this);
+ hLayout = new QHBoxLayout;
+ sidebarLayout = new QVBoxLayout;
+ toolboxLayout = new QGridLayout;
+
+ scene = new QGraphicsScene(this);
+ scene->setSceneRect(0, 0, 400, 400);
+
+ graphicsView = new GView(scene, this);
+ graphicsView->setMode(GView::Line);
+ connect(graphicsView, SIGNAL(lastItemChanged(QGraphicsItem*, int, GView::Type)), this, SLOT(handleLastItemChanged(QGraphicsItem*, int, GView::Type)));
+ connect(graphicsView, SIGNAL(itemDeleted(QString, int)), this, SLOT(handleItemDeleted(QString, int)));
+
+ widthBox = new QSpinBox(this);
+ connect(widthBox, SIGNAL(valueChanged(int)), this, SLOT(changeLineWidth(int)));
+ connect(graphicsView, SIGNAL(lineWidthChanged(int)), widthBox, SLOT(setValue(int)));
+ widthBox->setValue(1);
+
+ moveUpButton = new QPushButton("Move Up", this);
+ connect(moveUpButton, SIGNAL(clicked()), graphicsView, SLOT(moveUpSelectedItem()));
+
+ moveDownButton = new QPushButton("Move Down", this);
+ connect(moveDownButton, SIGNAL(clicked()), graphicsView, SLOT(moveDownSelectedItem()));
+
+ strokeLayout = new QHBoxLayout;
+ strokeColor = new ColorWidget;
+ strokeLayout->addWidget(new QLabel("Stroke:"));
+ strokeLayout->addWidget(strokeColor);
+ connect(strokeColor, SIGNAL(clicked()), this, SLOT(showColorDialog()));
+ connect(graphicsView, SIGNAL(lineColorChanged(QColor)), strokeColor, SLOT(setColor(QColor)));
+
+ fillLayout = new QHBoxLayout;
+ fillColor = new ColorWidget;
+ fillLayout->addWidget(new QLabel("Fill:"));
+ fillLayout->addWidget(fillColor);
+ connect(fillColor, SIGNAL(clicked()), this, SLOT(showBrushColorDialog()));
+ connect(graphicsView, SIGNAL(brushColorChanged(QColor)), fillColor, SLOT(setColor(QColor)));
+
+ rubberButton = new QToolButton(this);
+ rubberButton->setIcon(QIcon(":/icons/eraser.png"));
+ rubberButton->setCheckable(true);
+ rubberButton->setAutoExclusive(true);
+ connect(rubberButton, SIGNAL(clicked()), this, SLOT(setRubberMode()));
+
+ lineButton = new QToolButton(this);
+ lineButton->setIcon(QIcon(":/icons/line.png"));
+ lineButton->setCheckable(true);
+ lineButton->setAutoExclusive(true);
+ lineButton->setChecked(true);
+ connect(lineButton, SIGNAL(clicked()), this, SLOT(setLineMode()));
+
+ rectButton = new QToolButton(this);
+ rectButton->setIcon(QIcon(":/icons/rect.png"));
+ rectButton->setCheckable(true);
+ rectButton->setAutoExclusive(true);
+ connect(rectButton, SIGNAL(clicked()), this, SLOT(setRectMode()));
+
+ circleButton = new QToolButton(this);
+ circleButton->setIcon(QIcon(":/icons/circle.png"));
+ circleButton->setCheckable(true);
+ circleButton->setAutoExclusive(true);
+ connect(circleButton, SIGNAL(clicked()), this, SLOT(setCircleMode()));
+
+ handLineButton = new QToolButton(this);
+ handLineButton->setIcon(QIcon(":/icons/handline.png"));
+ handLineButton->setCheckable(true);
+ handLineButton->setAutoExclusive(true);
+ connect(handLineButton, SIGNAL(clicked()), this, SLOT(setHandLineMode()));
+
+ textButton = new QToolButton(this);
+ textButton->setIcon(QIcon(":/icons/text.png"));
+ textButton->setCheckable(true);
+ textButton->setAutoExclusive(true);
+ connect(textButton, SIGNAL(clicked()), this, SLOT(setTextMode()));
+
+ polygonButton = new QToolButton(this);
+ polygonButton->setIcon(QIcon(":/icons/polygon.png"));
+ polygonButton->setCheckable(true);
+ polygonButton->setAutoExclusive(true);
+ connect(polygonButton, SIGNAL(clicked()), this, SLOT(setPolygonMode()));
+
+ selectButton = new QToolButton(this);
+ selectButton->setIcon(QIcon(":/icons/cursor.png"));
+ selectButton->setCheckable(true);
+ selectButton->setAutoExclusive(true);
+ connect(selectButton, SIGNAL(clicked()), this, SLOT(setSelectMode()));
+
+ toolboxLayout->addWidget(rubberButton, 0, 0);
+ toolboxLayout->addWidget(selectButton, 0, 1);
+ toolboxLayout->addWidget(lineButton, 0, 2);
+ toolboxLayout->addWidget(circleButton, 1, 0);
+ toolboxLayout->addWidget(handLineButton, 1, 1);
+ toolboxLayout->addWidget(rectButton, 1, 2);
+ toolboxLayout->addWidget(textButton, 2, 0);
+ toolboxLayout->addWidget(polygonButton, 2, 1);
+
+ sidebarLayout->addLayout(toolboxLayout);
+ sidebarLayout->addSpacing(30);
+ sidebarLayout->addWidget(moveUpButton);
+ sidebarLayout->addWidget(moveDownButton);
+ sidebarLayout->addSpacing(40);
+ sidebarLayout->addWidget(widthBox);
+ sidebarLayout->addLayout(strokeLayout);
+ sidebarLayout->addLayout(fillLayout);
+ sidebarLayout->addStretch();
+ hLayout->addWidget(graphicsView);
+ hLayout->addLayout(sidebarLayout);
+ layout->addLayout(hLayout);
+ this->setLayout(layout);
+
+ setSession(whiteboardSession);
+ }
+
+ void QtWhiteboardWindow::handleWhiteboardOperationReceive(const WhiteboardOperation::ref operation) {
+ WhiteboardInsertOperation::ref insertOp = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(operation);
+ if (insertOp) {
+ WhiteboardElementDrawingVisitor visitor(graphicsView, operation->getPos(), GView::New);
+ insertOp->getElement()->accept(visitor);
+ }
+
+ WhiteboardUpdateOperation::ref updateOp = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(operation);
+ if (updateOp) {
+ WhiteboardElementDrawingVisitor visitor(graphicsView, operation->getPos(), GView::Update);
+ updateOp->getElement()->accept(visitor);
+ if (updateOp->getPos() != updateOp->getNewPos()) {
+ graphicsView->move(graphicsView->getItem(P2QSTRING(updateOp->getElement()->getID())), updateOp->getNewPos());
+ }
+ }
+
+ WhiteboardDeleteOperation::ref deleteOp = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(operation);
+ if (deleteOp) {
+ graphicsView->deleteItem(P2QSTRING(deleteOp->getElementID()));
+ }
+ }
+
+ void QtWhiteboardWindow::changeLineWidth(int i)
+ {
+ graphicsView->setLineWidth(i);
+ }
+
+ void QtWhiteboardWindow::showColorDialog()
+ {
+ QColor color = QColorDialog::getColor(graphicsView->getLineColor(), 0, "Select pen color", QColorDialog::ShowAlphaChannel);
+ if(color.isValid())
+ graphicsView->setLineColor(color);
+ }
+
+ void QtWhiteboardWindow::showBrushColorDialog()
+ {
+ QColor color = QColorDialog::getColor(graphicsView->getBrushColor(), 0, "Select brush color", QColorDialog::ShowAlphaChannel);
+ if(color.isValid())
+ graphicsView->setBrushColor(color);
+ }
+
+ void QtWhiteboardWindow::setRubberMode()
+ {
+ graphicsView->setMode(GView::Rubber);
+ }
+
+ void QtWhiteboardWindow::setLineMode()
+ {
+ graphicsView->setMode(GView::Line);
+ }
+
+ void QtWhiteboardWindow::setRectMode()
+ {
+ graphicsView->setMode(GView::Rect);
+ }
+
+ void QtWhiteboardWindow::setCircleMode()
+ {
+ graphicsView->setMode(GView::Circle);
+ }
+
+ void QtWhiteboardWindow::setHandLineMode()
+ {
+ graphicsView->setMode(GView::HandLine);
+ }
+
+ void QtWhiteboardWindow::setTextMode()
+ {
+ graphicsView->setMode(GView::Text);
+ }
+
+ void QtWhiteboardWindow::setPolygonMode()
+ {
+ graphicsView->setMode(GView::Polygon);
+ }
+
+ void QtWhiteboardWindow::setSelectMode()
+ {
+ graphicsView->setMode(GView::Select);
+ }
+
+ void QtWhiteboardWindow::show()
+ {
+ QWidget::show();
+ }
+
+ void QtWhiteboardWindow::setSession(WhiteboardSession::ref session) {
+ graphicsView->clear();
+ whiteboardSession_ = session;
+ whiteboardSession_->onOperationReceived.connect(boost::bind(&QtWhiteboardWindow::handleWhiteboardOperationReceive, this, _1));
+ whiteboardSession_->onRequestAccepted.connect(boost::bind(&QWidget::show, this));
+ whiteboardSession_->onSessionTerminated.connect(boost::bind(&QtWhiteboardWindow::handleSessionTerminate, this));
+ }
+
+ void QtWhiteboardWindow::activateWindow() {
+ QWidget::activateWindow();
+ }
+
+ void QtWhiteboardWindow::setName(const std::string& name) {
+ setWindowTitle(P2QSTRING(name));
+ }
+
+ void QtWhiteboardWindow::handleLastItemChanged(QGraphicsItem* item, int pos, GView::Type type) {
+ WhiteboardElement::ref el;
+ QGraphicsLineItem* lineItem = qgraphicsitem_cast<QGraphicsLineItem*>(item);
+ if (lineItem != 0) {
+ QLine line = lineItem->line().toLine();
+ QColor color = lineItem->pen().color();
+ WhiteboardLineElement::ref element = boost::make_shared<WhiteboardLineElement>(line.x1()+lineItem->pos().x(), line.y1()+lineItem->pos().y(), line.x2()+lineItem->pos().x(), line.y2()+lineItem->pos().y());
+ element->setColor(WhiteboardColor(color.red(), color.green(), color.blue(), color.alpha()));
+ element->setPenWidth(lineItem->pen().width());
+
+ element->setID(lineItem->data(100).toString().toStdString());
+ el = element;
+ }
+
+ FreehandLineItem* freehandLineItem = qgraphicsitem_cast<FreehandLineItem*>(item);
+ if (freehandLineItem != 0) {
+ WhiteboardFreehandPathElement::ref element = boost::make_shared<WhiteboardFreehandPathElement>();
+ QColor color = freehandLineItem->pen().color();
+ std::vector<std::pair<int, int> > points;
+ QVector<QPointF>::const_iterator it = freehandLineItem->points().constBegin();
+ for ( ; it != freehandLineItem->points().constEnd(); ++it) {
+ points.push_back(std::pair<int, int>(it->x()+item->pos().x(), it->y()+item->pos().y()));
+ }
+
+ element->setColor(WhiteboardColor(color.red(), color.green(), color.blue(), color.alpha()));
+ element->setPenWidth(freehandLineItem->pen().width());
+ element->setPoints(points);
+
+ element->setID(freehandLineItem->data(100).toString().toStdString());
+ el = element;
+ }
+
+ QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item);
+ if (rectItem != 0) {
+ QRectF rect = rectItem->rect();
+ WhiteboardRectElement::ref element = boost::make_shared<WhiteboardRectElement>(rect.x()+item->pos().x(), rect.y()+item->pos().y(), rect.width(), rect.height());
+ QColor penColor = rectItem->pen().color();
+ QColor brushColor = rectItem->brush().color();
+
+ element->setBrushColor(WhiteboardColor(brushColor.red(), brushColor.green(), brushColor.blue(), brushColor.alpha()));
+ element->setPenColor(WhiteboardColor(penColor.red(), penColor.green(), penColor.blue(), penColor.alpha()));
+ element->setPenWidth(rectItem->pen().width());
+
+ element->setID(rectItem->data(100).toString().toStdString());
+ el = element;
+ }
+
+ QGraphicsTextItem* textItem = qgraphicsitem_cast<QGraphicsTextItem*>(item);
+ if (textItem != 0) {
+ QPointF point = textItem->pos();
+ WhiteboardTextElement::ref element = boost::make_shared<WhiteboardTextElement>(point.x(), point.y());
+ element->setText(textItem->toPlainText().toStdString());
+ element->setSize(textItem->font().pointSize());
+ QColor color = textItem->defaultTextColor();
+ element->setColor(WhiteboardColor(color.red(), color.green(), color.blue(), color.alpha()));
+
+ element->setID(textItem->data(100).toString().toStdString());
+ el = element;
+ }
+
+ QGraphicsPolygonItem* polygonItem = qgraphicsitem_cast<QGraphicsPolygonItem*>(item);
+ if (polygonItem) {
+ WhiteboardPolygonElement::ref element = boost::make_shared<WhiteboardPolygonElement>();
+ QPolygonF polygon = polygonItem->polygon();
+ std::vector<std::pair<int, int> > points;
+ QVector<QPointF>::const_iterator it = polygon.begin();
+ for (; it != polygon.end(); ++it) {
+ points.push_back(std::pair<int, int>(it->x()+item->pos().x(), it->y()+item->pos().y()));
+ }
+
+ element->setPoints(points);
+
+ QColor penColor = polygonItem->pen().color();
+ QColor brushColor = polygonItem->brush().color();
+ element->setPenColor(WhiteboardColor(penColor.red(), penColor.green(), penColor.blue(), penColor.alpha()));
+ element->setBrushColor(WhiteboardColor(brushColor.red(), brushColor.green(), brushColor.blue(), brushColor.alpha()));
+ element->setPenWidth(polygonItem->pen().width());
+
+ element->setID(polygonItem->data(100).toString().toStdString());
+ el = element;
+ }
+
+ QGraphicsEllipseItem* ellipseItem = qgraphicsitem_cast<QGraphicsEllipseItem*>(item);
+ if (ellipseItem) {
+ QRectF rect = ellipseItem->rect();
+ int cx = rect.x()+rect.width()/2 + item->pos().x();
+ int cy = rect.y()+rect.height()/2 + item->pos().y();
+ int rx = rect.width()/2;
+ int ry = rect.height()/2;
+ WhiteboardEllipseElement::ref element = boost::make_shared<WhiteboardEllipseElement>(cx, cy, rx, ry);
+
+ QColor penColor = ellipseItem->pen().color();
+ QColor brushColor = ellipseItem->brush().color();
+ element->setPenColor(WhiteboardColor(penColor.red(), penColor.green(), penColor.blue(), penColor.alpha()));
+ element->setBrushColor(WhiteboardColor(brushColor.red(), brushColor.green(), brushColor.blue(), brushColor.alpha()));
+ element->setPenWidth(ellipseItem->pen().width());
+
+ element->setID(ellipseItem->data(100).toString().toStdString());
+ el = element;
+ }
+
+ if (type == GView::New) {
+ WhiteboardInsertOperation::ref insertOp = boost::make_shared<WhiteboardInsertOperation>();
+ insertOp->setPos(pos);
+ insertOp->setElement(el);
+ whiteboardSession_->sendOperation(insertOp);
+ } else {
+ WhiteboardUpdateOperation::ref updateOp = boost::make_shared<WhiteboardUpdateOperation>();
+ updateOp->setPos(pos);
+ if (type == GView::Update) {
+ updateOp->setNewPos(pos);
+ } else if (type == GView::MoveUp) {
+ updateOp->setNewPos(pos+1);
+ } else if (type == GView::MoveDown) {
+ updateOp->setNewPos(pos-1);
+ }
+ updateOp->setElement(el);
+ whiteboardSession_->sendOperation(updateOp);
+ }
+ }
+
+ void QtWhiteboardWindow::handleItemDeleted(QString id, int pos) {
+ WhiteboardDeleteOperation::ref deleteOp = boost::make_shared<WhiteboardDeleteOperation>();
+ deleteOp->setElementID(Q2PSTRING(id));
+ deleteOp->setPos(pos);
+ whiteboardSession_->sendOperation(deleteOp);
+ }
+
+ void QtWhiteboardWindow::handleSessionTerminate() {
+ hide();
+ }
+
+ void QtWhiteboardWindow::closeEvent(QCloseEvent* event) {
+ QMessageBox box(this);
+ box.setText(tr("Closing window is equivalent closing the session. Are you sure you want to do this?"));
+ box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+ box.setIcon(QMessageBox::Question);
+ if (box.exec() == QMessageBox::Yes) {
+ whiteboardSession_->cancel();
+ } else {
+ event->ignore();
+ }
+ }
+}
diff --git a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h
new file mode 100644
index 0000000..4665ef0
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIInterfaces/WhiteboardWindow.h>
+#include <Swiften/Elements/Message.h>
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+
+#include <QWidget>
+#include <QGraphicsView>
+#include <QGraphicsScene>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QGridLayout>
+#include <QPainter>
+#include <QPushButton>
+#include <QSpinBox>
+#include <QColorDialog>
+#include <QToolButton>
+#include <QCloseEvent>
+
+#include "GView.h"
+#include "ColorWidget.h"
+
+namespace Swift {
+ class QtWhiteboardWindow : public QWidget, public WhiteboardWindow
+ {
+ Q_OBJECT;
+ public:
+ QtWhiteboardWindow(WhiteboardSession::ref whiteboardSession);
+ void show();
+ void setSession(WhiteboardSession::ref session);
+ void activateWindow();
+ void setName(const std::string& name);
+
+ private slots:
+ void changeLineWidth(int i);
+ void showColorDialog();
+ void showBrushColorDialog();
+ void setRubberMode();
+ void setLineMode();
+ void setRectMode();
+ void setCircleMode();
+ void setHandLineMode();
+ void setTextMode();
+ void setPolygonMode();
+ void setSelectMode();
+ void handleLastItemChanged(QGraphicsItem* item, int pos, GView::Type type);
+ void handleItemDeleted(QString id, int pos);
+
+ private:
+ void handleSessionTerminate();
+ void handleWhiteboardOperationReceive(const WhiteboardOperation::ref operation);
+ void closeEvent(QCloseEvent* event);
+
+ private:
+ QGraphicsScene* scene;
+ GView* graphicsView;
+ QVBoxLayout* layout;
+ QVBoxLayout* sidebarLayout;
+ QHBoxLayout* hLayout;
+ QGridLayout* toolboxLayout;
+ QHBoxLayout* strokeLayout;
+ QHBoxLayout* fillLayout;
+ ColorWidget* strokeColor;
+ ColorWidget* fillColor;
+ QWidget* widget;
+ QPushButton* moveUpButton;
+ QPushButton* moveDownButton;
+ QSpinBox* widthBox;
+ QToolButton* rubberButton;
+ QToolButton* lineButton;
+ QToolButton* rectButton;
+ QToolButton* circleButton;
+ QToolButton* handLineButton;
+ QToolButton* textButton;
+ QToolButton* polygonButton;
+ QToolButton* selectButton;
+
+ std::string lastOpID;
+ WhiteboardSession::ref whiteboardSession_;
+ };
+}
diff --git a/Swift/QtUI/Whiteboard/TextDialog.cpp b/Swift/QtUI/Whiteboard/TextDialog.cpp
new file mode 100644
index 0000000..021895a
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/TextDialog.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "TextDialog.h"
+
+namespace Swift {
+ TextDialog::TextDialog(QGraphicsTextItem* item, QWidget* parent) : QDialog(parent)
+ {
+ this->item = item;
+
+ layout = new QVBoxLayout(this);
+ hLayout = new QHBoxLayout;
+
+ editor = new QLineEdit(this);
+ connect(editor, SIGNAL(textChanged(const QString&)), this, SLOT(changeItemText(const QString&)));
+
+ fontSizeBox = new QSpinBox(this);
+ fontSizeBox->setMinimum(1);
+ connect(fontSizeBox, SIGNAL(valueChanged(int)), this, SLOT(changeItemFontSize(int)));
+ fontSizeBox->setValue(13);
+
+
+ buttonBox = new QDialogButtonBox(this);
+ buttonBox->setStandardButtons(QDialogButtonBox::Ok);
+ connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
+
+ hLayout->addWidget(editor);
+ hLayout->addWidget(fontSizeBox);
+ layout->addLayout(hLayout);
+ layout->addWidget(buttonBox);
+ }
+
+ void TextDialog::changeItemText(const QString &text)
+ {
+ item->setPlainText(text);
+ }
+
+ void TextDialog::changeItemFontSize(int i)
+ {
+ QFont font = item->font();
+ font.setPointSize(i);
+ item->setFont(font);
+ }
+
+ void TextDialog::accept() {
+ emit accepted(item);
+ done(QDialog::Accepted);
+ }
+}
diff --git a/Swift/QtUI/Whiteboard/TextDialog.h b/Swift/QtUI/Whiteboard/TextDialog.h
new file mode 100644
index 0000000..f4d9a13
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/TextDialog.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QDialog>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QDialogButtonBox>
+#include <QLineEdit>
+#include <QGraphicsTextItem>
+#include <QSpinBox>
+
+#include <iostream>
+
+using namespace std;
+
+namespace Swift {
+ class TextDialog : public QDialog
+ {
+ Q_OBJECT;
+ public:
+ TextDialog(QGraphicsTextItem* item, QWidget* parent = 0);
+
+ private:
+ QGraphicsTextItem* item;
+ QLineEdit* editor;
+ QDialogButtonBox* buttonBox;
+ QVBoxLayout* layout;
+ QHBoxLayout* hLayout;
+ QSpinBox* fontSizeBox;
+
+ signals:
+ void accepted(QGraphicsTextItem* item);
+
+ private slots:
+ void accept();
+ void changeItemText(const QString &text);
+ void changeItemFontSize(int i);
+ };
+}
+
diff --git a/Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h b/Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h
new file mode 100644
index 0000000..74c6c1d
--- /dev/null
+++ b/Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardLineElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardTextElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h>
+#include <Swift/QtUI/Whiteboard/GView.h>
+#include <QtSwiftUtil.h>
+
+namespace Swift {
+ class WhiteboardElementDrawingVisitor : public WhiteboardElementVisitor {
+ public:
+ WhiteboardElementDrawingVisitor(GView* graphicsView, int pos, GView::Type type) : graphicsView_(graphicsView), pos_(pos), type_(type) {}
+
+ void visit(WhiteboardLineElement& element) {
+ QGraphicsLineItem *item;
+ if (type_ == GView::New) {
+ item = new QGraphicsLineItem(element.x1(), element.y1(), element.x2(), element.y2());
+ graphicsView_->addItem(item, P2QSTRING(element.getID()), pos_);
+ } else {
+ item = qgraphicsitem_cast<QGraphicsLineItem*>(graphicsView_->getItem(P2QSTRING(element.getID())));
+ QLineF line(element.x1(), element.y1(), element.x2(), element.y2());
+ item->setLine(line);
+ item->setPos(0,0);
+ graphicsView_->deselect(P2QSTRING(element.getID()));
+ }
+ if (item) {
+ QPen pen;
+ WhiteboardColor color = element.getColor();
+ pen.setColor(QColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()));
+ pen.setWidth(element.getPenWidth());
+ item->setPen(pen);
+ QString id = P2QSTRING(element.getID());
+ item->setData(100, id);
+ }
+ }
+
+ void visit(WhiteboardFreehandPathElement& element) {
+ FreehandLineItem *item;
+ if (type_ == GView::New) {
+ item = new FreehandLineItem;
+ } else {
+ item = qgraphicsitem_cast<FreehandLineItem*>(graphicsView_->getItem(P2QSTRING(element.getID())));
+ item->setPos(0,0);
+ graphicsView_->deselect(P2QSTRING(element.getID()));
+ }
+
+ if (item) {
+ QPen pen;
+ WhiteboardColor color = element.getColor();
+ pen.setColor(QColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()));
+ pen.setWidth(element.getPenWidth());
+ item->setPen(pen);
+
+ std::vector<std::pair<int, int> >::const_iterator it = element.getPoints().begin();
+ item->setStartPoint(QPointF(it->first, it->second));
+ for (++it; it != element.getPoints().end(); ++it) {
+ item->lineTo(QPointF(it->first, it->second));
+ }
+
+ QString id = P2QSTRING(element.getID());
+ item->setData(100, id);
+ }
+ if (type_ == GView::New) {
+ graphicsView_->addItem(item, P2QSTRING(element.getID()), pos_);
+ }
+ }
+
+ void visit(WhiteboardRectElement& element) {
+ QGraphicsRectItem* item;
+ if (type_ == GView::New) {
+ item = new QGraphicsRectItem(element.getX(), element.getY(), element.getWidth(), element.getHeight());
+ graphicsView_->addItem(item, P2QSTRING(element.getID()), pos_);
+ } else {
+ item = qgraphicsitem_cast<QGraphicsRectItem*>(graphicsView_->getItem(P2QSTRING(element.getID())));
+ QRectF rect(element.getX(), element.getY(), element.getWidth(), element.getHeight());
+ item->setRect(rect);
+ item->setPos(0,0);
+ graphicsView_->deselect(P2QSTRING(element.getID()));
+ }
+
+ if (item) {
+ QPen pen;
+ QBrush brush(Qt::SolidPattern);
+ WhiteboardColor penColor = element.getPenColor();
+ WhiteboardColor brushColor = element.getBrushColor();
+ pen.setColor(QColor(penColor.getRed(), penColor.getGreen(), penColor.getBlue(), penColor.getAlpha()));
+ pen.setWidth(element.getPenWidth());
+ brush.setColor(QColor(brushColor.getRed(), brushColor.getGreen(), brushColor.getBlue(), brushColor.getAlpha()));
+ item->setPen(pen);
+ item->setBrush(brush);
+ QString id = P2QSTRING(element.getID());
+ item->setData(100, id);
+ }
+ }
+
+ void visit(WhiteboardPolygonElement& element) {
+ QGraphicsPolygonItem* item = qgraphicsitem_cast<QGraphicsPolygonItem*>(graphicsView_->getItem(P2QSTRING(element.getID())));
+ if (item == 0 && type_ == GView::New) {
+ item = new QGraphicsPolygonItem();
+ QString id = P2QSTRING(element.getID());
+ item->setData(100, id);
+ graphicsView_->addItem(item, id, pos_);
+ }
+ graphicsView_->deselect(P2QSTRING(element.getID()));
+ QPen pen;
+ QBrush brush(Qt::SolidPattern);
+ WhiteboardColor penColor = element.getPenColor();
+ WhiteboardColor brushColor = element.getBrushColor();
+ pen.setColor(QColor(penColor.getRed(), penColor.getGreen(), penColor.getBlue(), penColor.getAlpha()));
+ pen.setWidth(element.getPenWidth());
+ brush.setColor(QColor(brushColor.getRed(), brushColor.getGreen(), brushColor.getBlue(), brushColor.getAlpha()));
+ item->setPen(pen);
+ item->setBrush(brush);
+
+ item->setPos(0,0);
+ QPolygonF polygon;
+ std::vector<std::pair<int, int> >::const_iterator it = element.getPoints().begin();
+ for (; it != element.getPoints().end(); ++it) {
+ polygon.append(QPointF(it->first, it->second));
+ }
+ item->setPolygon(polygon);
+ }
+
+ void visit(WhiteboardTextElement& element) {
+ QGraphicsTextItem* item;
+ QString id = P2QSTRING(element.getID());
+ if (type_ == GView::New) {
+ item = new QGraphicsTextItem;
+ graphicsView_->addItem(item, id, pos_);
+ } else {
+ item = qgraphicsitem_cast<QGraphicsTextItem*>(graphicsView_->getItem(id));
+ graphicsView_->deselect(P2QSTRING(element.getID()));
+ }
+ if (item) {
+ item->setPlainText(P2QSTRING(element.getText()));
+ item->setPos(QPointF(element.getX(), element.getY()));
+ QFont font = item->font();
+ font.setPointSize(element.getSize());
+ item->setFont(font);
+ WhiteboardColor color = element.getColor();
+ item->setDefaultTextColor(QColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()));
+ item->setData(100, id);
+ }
+ }
+
+ void visit(WhiteboardEllipseElement& element) {
+ QRectF rect;
+ QGraphicsEllipseItem* item;
+ QString id = P2QSTRING(element.getID());
+ rect.setTopLeft(QPointF(element.getCX()-element.getRX(), element.getCY()-element.getRY()));
+ rect.setBottomRight(QPointF(element.getCX()+element.getRX(), element.getCY()+element.getRY()));
+ if (type_ == GView::New) {
+ item = new QGraphicsEllipseItem(rect);
+ graphicsView_->addItem(item, id, pos_);
+ } else {
+ item = qgraphicsitem_cast<QGraphicsEllipseItem*>(graphicsView_->getItem(id));
+ item->setRect(rect);
+ item->setPos(0,0);
+ graphicsView_->deselect(P2QSTRING(element.getID()));
+ }
+ QPen pen;
+ QBrush brush(Qt::SolidPattern);
+ WhiteboardColor penColor = element.getPenColor();
+ WhiteboardColor brushColor = element.getBrushColor();
+ pen.setColor(QColor(penColor.getRed(), penColor.getGreen(), penColor.getBlue(), penColor.getAlpha()));
+ pen.setWidth(element.getPenWidth());
+ brush.setColor(QColor(brushColor.getRed(), brushColor.getGreen(), brushColor.getBlue(), brushColor.getAlpha()));
+ item->setPen(pen);
+ item->setBrush(brush);
+ item->setData(100, id);
+ }
+
+ private:
+ GView* graphicsView_;
+ int pos_;
+ GView::Type type_;
+ };
+}
diff --git a/Swift/resources/icons/circle.png b/Swift/resources/icons/circle.png
new file mode 100644
index 0000000..001f4d6
--- /dev/null
+++ b/Swift/resources/icons/circle.png
Binary files differ
diff --git a/Swift/resources/icons/cursor.png b/Swift/resources/icons/cursor.png
new file mode 100644
index 0000000..9e6a7d3
--- /dev/null
+++ b/Swift/resources/icons/cursor.png
Binary files differ
diff --git a/Swift/resources/icons/eraser.png b/Swift/resources/icons/eraser.png
new file mode 100644
index 0000000..b9b7522
--- /dev/null
+++ b/Swift/resources/icons/eraser.png
Binary files differ
diff --git a/Swift/resources/icons/handline.png b/Swift/resources/icons/handline.png
new file mode 100644
index 0000000..a085c14
--- /dev/null
+++ b/Swift/resources/icons/handline.png
Binary files differ
diff --git a/Swift/resources/icons/line.png b/Swift/resources/icons/line.png
new file mode 100644
index 0000000..3b6cf31
--- /dev/null
+++ b/Swift/resources/icons/line.png
Binary files differ
diff --git a/Swift/resources/icons/polygon.png b/Swift/resources/icons/polygon.png
new file mode 100644
index 0000000..da2af14
--- /dev/null
+++ b/Swift/resources/icons/polygon.png
Binary files differ
diff --git a/Swift/resources/icons/rect.png b/Swift/resources/icons/rect.png
new file mode 100644
index 0000000..6bdbea2
--- /dev/null
+++ b/Swift/resources/icons/rect.png
Binary files differ
diff --git a/Swift/resources/icons/text.png b/Swift/resources/icons/text.png
new file mode 100644
index 0000000..ddd76a0
--- /dev/null
+++ b/Swift/resources/icons/text.png
Binary files differ
diff --git a/Swiften/Base/String.cpp b/Swiften/Base/String.cpp
index 7ddf614..242b8e5 100644
--- a/Swiften/Base/String.cpp
+++ b/Swiften/Base/String.cpp
@@ -6,6 +6,8 @@
#include <cassert>
#include <algorithm>
+#include <sstream>
+#include <iomanip>
#include <Swiften/Base/String.h>
@@ -95,4 +97,20 @@ std::vector<std::string> String::split(const std::string& s, char c) {
return result;
}
+std::string String::convertIntToHexString(int h) {
+ std::stringstream ss;
+ ss << std::setbase(16);
+ ss << h;
+ return ss.str();
+}
+
+int String::convertHexStringToInt(const std::string& s) {
+ std::stringstream ss;
+ int h;
+ ss << std::setbase(16);
+ ss << s;
+ ss >> h;
+ return h;
+}
+
}
diff --git a/Swiften/Base/String.h b/Swiften/Base/String.h
index 26cc3f4..de181a1 100644
--- a/Swiften/Base/String.h
+++ b/Swiften/Base/String.h
@@ -29,6 +29,9 @@ namespace Swift {
inline bool endsWith(const std::string& s, char c) {
return s.size() > 0 && s[s.size()-1] == c;
}
+
+ std::string convertIntToHexString(int h);
+ int convertHexStringToInt(const std::string& s);
};
class SWIFTEN_API makeString {
diff --git a/Swiften/Client/Client.cpp b/Swiften/Client/Client.cpp
index 4d3ee04..1a6c64b 100644
--- a/Swiften/Client/Client.cpp
+++ b/Swiften/Client/Client.cpp
@@ -29,6 +29,7 @@
#include <Swiften/Jingle/JingleSessionManager.h>
#include <Swiften/Network/NetworkFactories.h>
#include <Swiften/FileTransfer/FileTransferManagerImpl.h>
+#include <Swiften/Whiteboard/WhiteboardSessionManager.h>
#ifndef SWIFT_EXPERIMENTAL_FT
#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h>
#endif
@@ -68,9 +69,16 @@ Client::Client(const JID& jid, const SafeString& password, NetworkFactories* net
jingleSessionManager = new JingleSessionManager(getIQRouter());
fileTransferManager = NULL;
+
+ whiteboardSessionManager = NULL;
+#ifdef SWIFT_EXPERIMENTAL_WB
+ whiteboardSessionManager = new WhiteboardSessionManager(getIQRouter(), getStanzaChannel(), presenceOracle, getEntityCapsProvider());
+#endif
}
Client::~Client() {
+ delete whiteboardSessionManager;
+
delete fileTransferManager;
delete jingleSessionManager;
@@ -164,4 +172,8 @@ FileTransferManager* Client::getFileTransferManager() const {
return fileTransferManager;
}
+WhiteboardSessionManager* Client::getWhiteboardSessionManager() const {
+ return whiteboardSessionManager;
+}
+
}
diff --git a/Swiften/Client/Client.h b/Swiften/Client/Client.h
index 9652b16..126572a 100644
--- a/Swiften/Client/Client.h
+++ b/Swiften/Client/Client.h
@@ -36,6 +36,7 @@ namespace Swift {
class FileTransferManager;
class JingleSessionManager;
class FileTransferManager;
+ class WhiteboardSessionManager;
/**
* Provides the core functionality for writing XMPP client software.
@@ -150,6 +151,8 @@ namespace Swift {
* using setCertificateTrustChecker().
*/
void setAlwaysTrustCertificates();
+
+ WhiteboardSessionManager* getWhiteboardSessionManager() const;
public:
/**
@@ -185,5 +188,6 @@ namespace Swift {
JingleSessionManager* jingleSessionManager;
FileTransferManager* fileTransferManager;
BlindCertificateTrustChecker* blindCertificateTrustChecker;
+ WhiteboardSessionManager* whiteboardSessionManager;
};
}
diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp
index a4ce079..1683916 100644
--- a/Swiften/Elements/DiscoInfo.cpp
+++ b/Swiften/Elements/DiscoInfo.cpp
@@ -22,6 +22,7 @@ const std::string DiscoInfo::JingleTransportsIBBFeature = std::string("urn:xmpp:
const std::string DiscoInfo::JingleTransportsS5BFeature = std::string("urn:xmpp:jingle:transports:s5b:1");
const std::string DiscoInfo::Bytestream = std::string("http://jabber.org/protocol/bytestreams");
const std::string DiscoInfo::MessageDeliveryReceiptsFeature = std::string("urn:xmpp:receipts");
+const std::string DiscoInfo::WhiteboardFeature = std::string("http://swift.im/whiteboard");
bool DiscoInfo::Identity::operator<(const Identity& other) const {
if (category_ == other.category_) {
diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h
index 8add815..3334ac8 100644
--- a/Swiften/Elements/DiscoInfo.h
+++ b/Swiften/Elements/DiscoInfo.h
@@ -33,6 +33,7 @@ namespace Swift {
static const std::string JingleTransportsS5BFeature;
static const std::string Bytestream;
static const std::string MessageDeliveryReceiptsFeature;
+ static const std::string WhiteboardFeature;
class Identity {
public:
diff --git a/Swiften/Elements/Whiteboard/WhiteboardColor.cpp b/Swiften/Elements/Whiteboard/WhiteboardColor.cpp
new file mode 100644
index 0000000..f4ff01a
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardColor.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+#include <Swiften/Base/String.h>
+#include <cstdio>
+#include <iomanip>
+#include <sstream>
+#include <iostream>
+
+namespace Swift {
+ WhiteboardColor::WhiteboardColor() : red_(0), green_(0), blue_(0), alpha_(255) {
+ }
+
+ WhiteboardColor::WhiteboardColor(int red, int green, int blue, int alpha) : red_(red), green_(green), blue_(blue), alpha_(alpha) {
+ }
+
+ WhiteboardColor::WhiteboardColor(const std::string& hex) : alpha_(255) {
+ int value = String::convertHexStringToInt(hex.substr(1));
+ red_ = (value >> 16)&0xFF;
+ green_ = (value >> 8)&0xFF;
+ blue_ = value&0xFF;
+ }
+
+ std::string WhiteboardColor::toHex() const {
+ std::string value = String::convertIntToHexString((red_ << 16) + (green_ << 8) + blue_);
+ while (value.size() < 6) {
+ value.insert(0, "0");
+ }
+ return "#"+value;
+ }
+
+ int WhiteboardColor::getRed() const {
+ return red_;
+ }
+
+ int WhiteboardColor::getGreen() const {
+ return green_;
+ }
+
+ int WhiteboardColor::getBlue() const {
+ return blue_;
+ }
+
+ int WhiteboardColor::getAlpha() const {
+ return alpha_;
+ }
+
+ void WhiteboardColor::setAlpha(int alpha) {
+ alpha_ = alpha;
+ }
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardColor.h b/Swiften/Elements/Whiteboard/WhiteboardColor.h
new file mode 100644
index 0000000..a940338
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardColor.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace Swift {
+ class WhiteboardColor {
+ public:
+ WhiteboardColor();
+ WhiteboardColor(int red, int green, int blue, int alpha = 255);
+ WhiteboardColor(const std::string& hex);
+ std::string toHex() const;
+ int getRed() const;
+ int getGreen() const;
+ int getBlue() const;
+ int getAlpha() const;
+ void setAlpha(int alpha);
+
+ private:
+ int red_, green_, blue_;
+ int alpha_;
+ };
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h b/Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h
new file mode 100644
index 0000000..091ee30
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+
+namespace Swift {
+ class WhiteboardDeleteOperation : public WhiteboardOperation {
+ public:
+ typedef boost::shared_ptr<WhiteboardDeleteOperation> ref;
+ public:
+ ~WhiteboardDeleteOperation() {
+ }
+
+ std::string getElementID() const {
+ return elementID_;
+ }
+
+ void setElementID(const std::string& elementID) {
+ elementID_ = elementID;
+ }
+
+ private:
+ std::string elementID_;
+ };
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardElement.h b/Swiften/Elements/Whiteboard/WhiteboardElement.h
new file mode 100644
index 0000000..df774d9
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardElement.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+#include <Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h>
+
+namespace Swift {
+ class WhiteboardElement {
+ public:
+ typedef boost::shared_ptr<WhiteboardElement> ref;
+
+ public:
+ virtual ~WhiteboardElement() {}
+ virtual void accept(WhiteboardElementVisitor& visitor) = 0;
+
+ const std::string& getID() const {
+ return id_;
+ }
+
+ void setID(const std::string& id) {
+ id_ = id;
+ }
+
+ private:
+ std::string id_;
+ };
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h b/Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h
new file mode 100644
index 0000000..413d6cf
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+namespace Swift {
+ class WhiteboardLineElement;
+ class WhiteboardFreehandPathElement;
+ class WhiteboardRectElement;
+ class WhiteboardPolygonElement;
+ class WhiteboardTextElement;
+ class WhiteboardEllipseElement;
+
+ class WhiteboardElementVisitor {
+ public:
+ virtual ~WhiteboardElementVisitor() {}
+ virtual void visit(WhiteboardLineElement& /*element*/) = 0;
+ virtual void visit(WhiteboardFreehandPathElement& /*element*/) = 0;
+ virtual void visit(WhiteboardRectElement& /*element*/) = 0;
+ virtual void visit(WhiteboardPolygonElement& /*element*/) = 0;
+ virtual void visit(WhiteboardTextElement& /*element*/) = 0;
+ virtual void visit(WhiteboardEllipseElement& /*element*/) = 0;
+ };
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h b/Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h
new file mode 100644
index 0000000..0078479
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+
+namespace Swift {
+ class WhiteboardEllipseElement : public WhiteboardElement {
+ public:
+ typedef boost::shared_ptr<WhiteboardEllipseElement> ref;
+ public:
+ WhiteboardEllipseElement(int cx, int cy, int rx, int ry) {
+ cx_ = cx;
+ cy_ = cy;
+ rx_ = rx;
+ ry_ = ry;
+ }
+
+ int getCX() const {
+ return cx_;
+ }
+
+ int getCY() const {
+ return cy_;
+ }
+
+ int getRX() const {
+ return rx_;
+ }
+
+ int getRY() const {
+ return ry_;
+ }
+
+ const WhiteboardColor& getPenColor() const {
+ return penColor_;
+ }
+
+ void setPenColor(const WhiteboardColor& color) {
+ penColor_ = color;
+ }
+
+ const WhiteboardColor& getBrushColor() const {
+ return brushColor_;
+ }
+
+ void setBrushColor(const WhiteboardColor& color) {
+ brushColor_ = color;
+ }
+
+ int getPenWidth() const {
+ return penWidth_;
+ }
+
+ void setPenWidth(const int penWidth) {
+ penWidth_ = penWidth;
+ }
+
+ void accept(WhiteboardElementVisitor& visitor) {
+ visitor.visit(*this);
+ }
+
+ private:
+ int cx_, cy_, rx_, ry_;
+ WhiteboardColor penColor_;
+ WhiteboardColor brushColor_;
+ int penWidth_;
+ };
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h b/Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h
new file mode 100644
index 0000000..bcf3bf9
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+
+#include <vector>
+#include <utility>
+
+namespace Swift {
+ class WhiteboardFreehandPathElement : public WhiteboardElement {
+ typedef std::pair<int, int> Point;
+ public:
+ typedef boost::shared_ptr<WhiteboardFreehandPathElement> ref;
+ public:
+ WhiteboardFreehandPathElement() {
+ }
+
+ void setPoints(const std::vector<Point>& points) {
+ points_ = points;
+ }
+
+ const std::vector<Point>& getPoints() const {
+ return points_;
+ }
+
+ const WhiteboardColor& getColor() const {
+ return color_;
+ }
+
+ void setColor(const WhiteboardColor& color) {
+ color_ = color;
+ }
+
+ int getPenWidth() const {
+ return penWidth_;
+ }
+
+ void setPenWidth(const int penWidth) {
+ penWidth_ = penWidth;
+ }
+
+ void accept(WhiteboardElementVisitor& visitor) {
+ visitor.visit(*this);
+ }
+
+ private:
+ std::vector<Point> points_;
+ WhiteboardColor color_;
+ int penWidth_;
+ };
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h b/Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h
new file mode 100644
index 0000000..8c20044
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+
+namespace Swift {
+ class WhiteboardInsertOperation : public WhiteboardOperation {
+ public:
+ typedef boost::shared_ptr<WhiteboardInsertOperation> ref;
+ public:
+ ~WhiteboardInsertOperation() {
+ }
+
+ WhiteboardElement::ref getElement() const {
+ return element_;
+ }
+
+ void setElement(WhiteboardElement::ref element) {
+ element_ = element;
+ }
+
+ private:
+ WhiteboardElement::ref element_;
+ };
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardLineElement.h b/Swiften/Elements/Whiteboard/WhiteboardLineElement.h
new file mode 100644
index 0000000..3c63afc
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardLineElement.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+
+namespace Swift {
+ class WhiteboardLineElement : public WhiteboardElement {
+ public:
+ typedef boost::shared_ptr<WhiteboardLineElement> ref;
+ public:
+ WhiteboardLineElement(int x1, int y1, int x2, int y2) {
+ x1_ = x1;
+ y1_ = y1;
+ x2_ = x2;
+ y2_ = y2;
+ }
+
+ int x1() const {
+ return x1_;
+ }
+
+ int y1() const {
+ return y1_;
+ }
+
+ int x2() const {
+ return x2_;
+ }
+
+ int y2() const {
+ return y2_;
+ }
+
+ const WhiteboardColor& getColor() const {
+ return color_;
+ }
+
+ void setColor(const WhiteboardColor& color) {
+ color_ = color;
+ }
+
+ int getPenWidth() const {
+ return penWidth_;
+ }
+
+ void setPenWidth(const int penWidth) {
+ penWidth_ = penWidth;
+ }
+
+ void accept(WhiteboardElementVisitor& visitor) {
+ visitor.visit(*this);
+ }
+
+ private:
+ int x1_, y1_, x2_, y2_;
+ WhiteboardColor color_;
+ int penWidth_;
+ };
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardOperation.h b/Swiften/Elements/Whiteboard/WhiteboardOperation.h
new file mode 100644
index 0000000..02c6438
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardOperation.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/smart_ptr/shared_ptr.hpp>
+#include <string>
+
+namespace Swift {
+ class WhiteboardOperation {
+ public:
+ typedef boost::shared_ptr<WhiteboardOperation> ref;
+ public:
+ virtual ~WhiteboardOperation(){};
+
+ std::string getID() const {
+ return id_;
+ }
+
+ void setID(const std::string& id) {
+ id_ = id;
+ }
+
+ std::string getParentID() const {
+ return parentID_;
+ }
+
+ void setParentID(const std::string& parentID) {
+ parentID_ = parentID;
+ }
+
+ int getPos() const {
+ return pos_;
+ }
+
+ void setPos(int pos) {
+ pos_ = pos;
+ }
+
+ private:
+ std::string id_;
+ std::string parentID_;
+ int pos_;
+ };
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h b/Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h
new file mode 100644
index 0000000..679ac01
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+
+namespace Swift {
+ class WhiteboardPolygonElement : public WhiteboardElement {
+ typedef std::pair<int, int> Point;
+ public:
+ typedef boost::shared_ptr<WhiteboardPolygonElement> ref;
+ public:
+ WhiteboardPolygonElement() {
+ }
+
+ const std::vector<Point>& getPoints() const {
+ return points_;
+ }
+
+ void setPoints(const std::vector<Point>& points) {
+ points_ = points;
+ }
+
+ const WhiteboardColor& getPenColor() const {
+ return penColor_;
+ }
+
+ void setPenColor(const WhiteboardColor& color) {
+ penColor_ = color;
+ }
+
+ const WhiteboardColor& getBrushColor() const {
+ return brushColor_;
+ }
+
+ void setBrushColor(const WhiteboardColor& color) {
+ brushColor_ = color;
+ }
+
+ int getPenWidth() const {
+ return penWidth_;
+ }
+
+ void setPenWidth(const int penWidth) {
+ penWidth_ = penWidth;
+ }
+
+ void accept(WhiteboardElementVisitor& visitor) {
+ visitor.visit(*this);
+ }
+
+ private:
+ std::vector<Point> points_;
+ WhiteboardColor penColor_;
+ WhiteboardColor brushColor_;
+ int penWidth_;
+ };
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardRectElement.h b/Swiften/Elements/Whiteboard/WhiteboardRectElement.h
new file mode 100644
index 0000000..233b3fa
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardRectElement.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+
+namespace Swift {
+ class WhiteboardRectElement : public WhiteboardElement {
+ public:
+ typedef boost::shared_ptr<WhiteboardRectElement> ref;
+ public:
+ WhiteboardRectElement(int x, int y, int width, int height) {
+ x_ = x;
+ y_ = y;
+ width_ = width;
+ height_ = height;
+ }
+
+ int getX() const {
+ return x_;
+ }
+
+ int getY() const {
+ return y_;
+ }
+
+ int getWidth() const {
+ return width_;
+ }
+
+ int getHeight() const {
+ return height_;
+ }
+
+ const WhiteboardColor& getPenColor() const {
+ return penColor_;
+ }
+
+ void setPenColor(const WhiteboardColor& color) {
+ penColor_ = color;
+ }
+
+ const WhiteboardColor& getBrushColor() const {
+ return brushColor_;
+ }
+
+ void setBrushColor(const WhiteboardColor& color) {
+ brushColor_ = color;
+ }
+
+ int getPenWidth() const {
+ return penWidth_;
+ }
+
+ void setPenWidth(const int penWidth) {
+ penWidth_ = penWidth;
+ }
+
+ void accept(WhiteboardElementVisitor& visitor) {
+ visitor.visit(*this);
+ }
+
+ private:
+ int x_, y_, width_, height_;
+ WhiteboardColor penColor_;
+ WhiteboardColor brushColor_;
+ int penWidth_;
+ };
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardTextElement.h b/Swiften/Elements/Whiteboard/WhiteboardTextElement.h
new file mode 100644
index 0000000..7118ac9
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardTextElement.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+
+namespace Swift {
+ class WhiteboardTextElement : public WhiteboardElement {
+ public:
+ typedef boost::shared_ptr<WhiteboardTextElement> ref;
+ public:
+ WhiteboardTextElement(int x, int y) {
+ x_ = x;
+ y_ = y;
+ }
+
+ void setText(const std::string text) {
+ text_ = text;
+ }
+
+ const std::string& getText() const {
+ return text_;
+ }
+
+ int getX() const {
+ return x_;
+ }
+
+ int getY() const {
+ return y_;
+ }
+
+ const WhiteboardColor& getColor() const {
+ return color_;
+ }
+
+ void setColor(const WhiteboardColor& color) {
+ color_ = color;
+ }
+
+ int getSize() const {
+ return size_;
+ }
+
+ void setSize(const int size) {
+ size_ = size;
+ }
+
+ void accept(WhiteboardElementVisitor& visitor) {
+ visitor.visit(*this);
+ }
+
+ private:
+ int x_, y_;
+ int size_;
+ std::string text_;
+ WhiteboardColor color_;
+ };
+}
diff --git a/Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h b/Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h
new file mode 100644
index 0000000..a52a341
--- /dev/null
+++ b/Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+
+namespace Swift {
+ class WhiteboardUpdateOperation : public WhiteboardOperation {
+ public:
+ typedef boost::shared_ptr<WhiteboardUpdateOperation> ref;
+ public:
+ ~WhiteboardUpdateOperation() {
+ }
+
+ WhiteboardElement::ref getElement() const {
+ return element_;
+ }
+
+ void setElement(WhiteboardElement::ref element) {
+ element_ = element;
+ }
+
+ int getNewPos() const {
+ return newPos_;
+ }
+
+ void setNewPos(int newPos) {
+ newPos_ = newPos;
+ }
+
+ private:
+ WhiteboardElement::ref element_;
+ int newPos_;
+ };
+}
diff --git a/Swiften/Elements/WhiteboardPayload.h b/Swiften/Elements/WhiteboardPayload.h
new file mode 100644
index 0000000..ceb2b27
--- /dev/null
+++ b/Swiften/Elements/WhiteboardPayload.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <Swiften/Elements/Payload.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+
+namespace Swift {
+ class WhiteboardPayload : public Payload {
+ public:
+ typedef boost::shared_ptr<WhiteboardPayload> ref;
+
+ public:
+ enum Type {UnknownType, Data, SessionRequest, SessionAccept, SessionTerminate};
+
+ WhiteboardPayload(Type type = WhiteboardPayload::Data) : type_(type) {
+ }
+
+ void setData(const std::string &data) {
+ data_ = data;
+ }
+
+ std::string getData() const {
+ return data_;
+ }
+
+ Type getType() const {
+ return type_;
+ }
+
+ void setType(Type type) {
+ type_ = type;
+ }
+
+ WhiteboardElement::ref getElement() const {
+ return element_;
+ }
+
+ void setElement(WhiteboardElement::ref element) {
+ element_ = element;
+ }
+
+ WhiteboardOperation::ref getOperation() const {
+ return operation_;
+ }
+
+ void setOperation(WhiteboardOperation::ref operation) {
+ operation_ = operation;
+ }
+
+ private:
+ std::string data_;
+ Type type_;
+ WhiteboardElement::ref element_;
+ WhiteboardOperation::ref operation_;
+ };
+}
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index 217f278..a40e8f6 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -66,6 +66,7 @@
#include <Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h>
#include <Swiften/Parser/PayloadParsers/DeliveryReceiptParserFactory.h>
#include <Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParserFactory.h>
+#include <Swiften/Parser/PayloadParsers/WhiteboardParser.h>
using namespace boost;
@@ -124,6 +125,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleFileTransferReceivedParser> >("received", "urn:xmpp:jingle:apps:file-transfer:3"));
factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleFileTransferHashParser> >("checksum"));
factories_.push_back(boost::make_shared<GenericPayloadParserFactory<S5BProxyRequestParser> >("query", "http://jabber.org/protocol/bytestreams"));
+ factories_.push_back(boost::make_shared<GenericPayloadParserFactory<WhiteboardParser> >("wb", "http://swift.im/whiteboard"));
factories_.push_back(boost::make_shared<DeliveryReceiptParserFactory>());
factories_.push_back(boost::make_shared<DeliveryReceiptRequestParserFactory>());
diff --git a/Swiften/Parser/PayloadParsers/WhiteboardParser.cpp b/Swiften/Parser/PayloadParsers/WhiteboardParser.cpp
new file mode 100644
index 0000000..09d8de9
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/WhiteboardParser.cpp
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/WhiteboardParser.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardLineElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardRectElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardTextElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardColor.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h>
+#include <boost/optional.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace Swift {
+ WhiteboardParser::WhiteboardParser() : actualIsText(false), level_(0) {
+ }
+
+ void WhiteboardParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) {
+ if (level_ == 0) {
+ getPayloadInternal()->setType(stringToType(attributes.getAttributeValue("type").get_value_or("")));
+ } else if (level_ == 1) {
+ std::string type = attributes.getAttributeValue("type").get_value_or("");
+ if (type == "insert") {
+ WhiteboardInsertOperation::ref insertOp = boost::make_shared<WhiteboardInsertOperation>();
+ operation = insertOp;
+ } else if (type == "update") {
+ WhiteboardUpdateOperation::ref updateOp = boost::make_shared<WhiteboardUpdateOperation>();
+ std::string move = attributes.getAttributeValue("newpos").get_value_or("0");
+ updateOp->setNewPos(boost::lexical_cast<int>(attributes.getAttributeValue("newpos").get_value_or("0")));
+ operation = updateOp;
+ } else if (type == "delete") {
+ WhiteboardDeleteOperation::ref deleteOp = boost::make_shared<WhiteboardDeleteOperation>();
+ deleteOp->setElementID(attributes.getAttributeValue("elementid").get_value_or(""));
+ operation = deleteOp;
+ }
+ if (operation) {
+ try {
+ operation->setID(attributes.getAttributeValue("id").get_value_or(""));
+ operation->setParentID(attributes.getAttributeValue("parentid").get_value_or(""));
+ operation->setPos(boost::lexical_cast<int>(attributes.getAttributeValue("pos").get_value_or("0")));
+ } catch (boost::bad_lexical_cast&) {
+ }
+ }
+
+ } else if (level_ == 2) {
+ if (element == "line") {
+ int x1 = 0;
+ int y1 = 0;
+ int x2 = 0;
+ int y2 = 0;
+ try {
+ x1 = boost::lexical_cast<int>(attributes.getAttributeValue("x1").get_value_or("0"));
+ y1 = boost::lexical_cast<int>(attributes.getAttributeValue("y1").get_value_or("0"));
+ x2 = boost::lexical_cast<int>(attributes.getAttributeValue("x2").get_value_or("0"));
+ y2 = boost::lexical_cast<int>(attributes.getAttributeValue("y2").get_value_or("0"));
+ } catch (boost::bad_lexical_cast&) {
+ }
+ WhiteboardLineElement::ref whiteboardElement = boost::make_shared<WhiteboardLineElement>(x1, y1, x2, y2);
+
+ WhiteboardColor color(attributes.getAttributeValue("stroke").get_value_or("#000000"));
+ color.setAlpha(opacityToAlpha(attributes.getAttributeValue("opacity").get_value_or("1")));
+ whiteboardElement->setColor(color);
+
+ int penWidth = 1;
+ try {
+ penWidth = boost::lexical_cast<int>(attributes.getAttributeValue("stroke-width").get_value_or("1"));
+ } catch (boost::bad_lexical_cast&) {
+ }
+ whiteboardElement->setPenWidth(penWidth);
+ whiteboardElement->setID(attributes.getAttributeValue("id").get_value_or(""));
+ getPayloadInternal()->setElement(whiteboardElement);
+ wbElement = whiteboardElement;
+ } else if (element == "path") {
+ WhiteboardFreehandPathElement::ref whiteboardElement = boost::make_shared<WhiteboardFreehandPathElement>();
+ std::string pathData = attributes.getAttributeValue("d").get_value_or("");
+ std::vector<std::pair<int, int> > points;
+ if (pathData[0] == 'M') {
+ unsigned int pos = 1;
+ unsigned int npos;
+ int x, y;
+ if (pathData[pos] == ' ') {
+ pos++;
+ }
+ try {
+ npos = pathData.find(' ', pos);
+ x = boost::lexical_cast<int>(pathData.substr(pos, npos-pos));
+ pos = npos+1;
+ npos = pathData.find('L', pos);
+ y = boost::lexical_cast<int>(pathData.substr(pos, npos-pos));
+ pos = npos+1;
+ if (pathData[pos] == ' ') {
+ pos++;
+ }
+ points.push_back(std::pair<int, int>(x, y));
+ while (pos < pathData.size()) {
+ npos = pathData.find(' ', pos);
+ x = boost::lexical_cast<int>(pathData.substr(pos, npos-pos));
+ pos = npos+1;
+ npos = pathData.find(' ', pos);
+ y = boost::lexical_cast<int>(pathData.substr(pos, npos-pos));
+ pos = npos+1;
+ points.push_back(std::pair<int, int>(x, y));
+ }
+ } catch (boost::bad_lexical_cast&) {
+ }
+ }
+ whiteboardElement->setPoints(points);
+
+ int penWidth = 1;
+ try {
+ penWidth = boost::lexical_cast<int>(attributes.getAttributeValue("stroke-width").get_value_or("1"));
+ } catch (boost::bad_lexical_cast&) {
+ }
+ whiteboardElement->setPenWidth(penWidth);
+
+ WhiteboardColor color(attributes.getAttributeValue("stroke").get_value_or("#000000"));
+ color.setAlpha(opacityToAlpha(attributes.getAttributeValue("opacity").get_value_or("1")));
+ whiteboardElement->setColor(color);
+ whiteboardElement->setID(attributes.getAttributeValue("id").get_value_or(""));
+ getPayloadInternal()->setElement(whiteboardElement);
+ wbElement = whiteboardElement;
+ } else if (element == "rect") {
+ int x = 0;
+ int y = 0;
+ int width = 0;
+ int height = 0;
+ try {
+ x = boost::lexical_cast<int>(attributes.getAttributeValue("x").get_value_or("0"));
+ y = boost::lexical_cast<int>(attributes.getAttributeValue("y").get_value_or("0"));
+ width = boost::lexical_cast<int>(attributes.getAttributeValue("width").get_value_or("0"));
+ height = boost::lexical_cast<int>(attributes.getAttributeValue("height").get_value_or("0"));
+ } catch (boost::bad_lexical_cast&) {
+ }
+
+ WhiteboardRectElement::ref whiteboardElement = boost::make_shared<WhiteboardRectElement>(x, y, width, height);
+
+ int penWidth = 1;
+ try {
+ penWidth = boost::lexical_cast<int>(attributes.getAttributeValue("stroke-width").get_value_or("1"));
+ } catch (boost::bad_lexical_cast&) {
+ }
+ whiteboardElement->setPenWidth(penWidth);
+
+ WhiteboardColor penColor(attributes.getAttributeValue("stroke").get_value_or("#000000"));
+ WhiteboardColor brushColor(attributes.getAttributeValue("fill").get_value_or("#000000"));
+ penColor.setAlpha(opacityToAlpha(attributes.getAttributeValue("opacity").get_value_or("1")));
+ brushColor.setAlpha(opacityToAlpha(attributes.getAttributeValue("fill-opacity").get_value_or("1")));
+ whiteboardElement->setPenColor(penColor);
+ whiteboardElement->setBrushColor(brushColor);
+ whiteboardElement->setID(attributes.getAttributeValue("id").get_value_or(""));
+ getPayloadInternal()->setElement(whiteboardElement);
+ wbElement = whiteboardElement;
+ } else if (element == "polygon") {
+ WhiteboardPolygonElement::ref whiteboardElement = boost::make_shared<WhiteboardPolygonElement>();
+
+ std::string pointsData = attributes.getAttributeValue("points").get_value_or("");
+ std::vector<std::pair<int, int> > points;
+ unsigned int pos = 0;
+ unsigned int npos;
+ int x, y;
+ try {
+ while (pos < pointsData.size()) {
+ npos = pointsData.find(',', pos);
+ x = boost::lexical_cast<int>(pointsData.substr(pos, npos-pos));
+ pos = npos+1;
+ npos = pointsData.find(' ', pos);
+ y = boost::lexical_cast<int>(pointsData.substr(pos, npos-pos));
+ pos = npos+1;
+ points.push_back(std::pair<int, int>(x, y));
+ }
+ } catch (boost::bad_lexical_cast&) {
+ }
+
+ whiteboardElement->setPoints(points);
+
+ int penWidth = 1;
+ try {
+ penWidth = boost::lexical_cast<int>(attributes.getAttributeValue("stroke-width").get_value_or("1"));
+ } catch (boost::bad_lexical_cast&) {
+ }
+ whiteboardElement->setPenWidth(penWidth);
+
+ WhiteboardColor penColor(attributes.getAttributeValue("stroke").get_value_or("#000000"));
+ WhiteboardColor brushColor(attributes.getAttributeValue("fill").get_value_or("#000000"));
+ penColor.setAlpha(opacityToAlpha(attributes.getAttributeValue("opacity").get_value_or("1")));
+ brushColor.setAlpha(opacityToAlpha(attributes.getAttributeValue("fill-opacity").get_value_or("1")));
+ whiteboardElement->setPenColor(penColor);
+ whiteboardElement->setBrushColor(brushColor);
+ whiteboardElement->setID(attributes.getAttributeValue("id").get_value_or(""));
+ getPayloadInternal()->setElement(whiteboardElement);
+ wbElement = whiteboardElement;
+ } else if (element == "text") {
+ int x = 0;
+ int y = 0;
+ try {
+ x = boost::lexical_cast<int>(attributes.getAttributeValue("x").get_value_or("0"));
+ y = boost::lexical_cast<int>(attributes.getAttributeValue("y").get_value_or("0"));
+ } catch (boost::bad_lexical_cast&) {
+ }
+
+ WhiteboardTextElement::ref whiteboardElement = boost::make_shared<WhiteboardTextElement>(x, y);
+
+ actualIsText = true;
+ WhiteboardColor color(attributes.getAttributeValue("fill").get_value_or("#000000"));
+ color.setAlpha(opacityToAlpha(attributes.getAttributeValue("opacity").get_value_or("1")));
+ whiteboardElement->setColor(color);
+
+ int fontSize = 1;
+ try {
+ fontSize = boost::lexical_cast<int>(attributes.getAttributeValue("font-size").get_value_or("12"));
+ } catch (boost::bad_lexical_cast&) {
+ }
+ whiteboardElement->setSize(fontSize);
+ whiteboardElement->setID(attributes.getAttributeValue("id").get_value_or(""));
+ getPayloadInternal()->setElement(whiteboardElement);
+ wbElement = whiteboardElement;
+ } else if (element == "ellipse") {
+ int cx = 0;
+ int cy = 0;
+ int rx = 0;
+ int ry = 0;
+ try {
+ cx = boost::lexical_cast<int>(attributes.getAttributeValue("cx").get_value_or("0"));
+ cy = boost::lexical_cast<int>(attributes.getAttributeValue("cy").get_value_or("0"));
+ rx = boost::lexical_cast<int>(attributes.getAttributeValue("rx").get_value_or("0"));
+ ry = boost::lexical_cast<int>(attributes.getAttributeValue("ry").get_value_or("0"));
+ } catch (boost::bad_lexical_cast&) {
+ }
+
+ WhiteboardEllipseElement::ref whiteboardElement = boost::make_shared<WhiteboardEllipseElement>(cx, cy, rx, ry);
+
+ int penWidth = 1;
+ try {
+ penWidth = boost::lexical_cast<int>(attributes.getAttributeValue("stroke-width").get_value_or("1"));
+ } catch (boost::bad_lexical_cast&) {
+ }
+ whiteboardElement->setPenWidth(penWidth);
+
+ WhiteboardColor penColor(attributes.getAttributeValue("stroke").get_value_or("#000000"));
+ WhiteboardColor brushColor(attributes.getAttributeValue("fill").get_value_or("#000000"));
+ penColor.setAlpha(opacityToAlpha(attributes.getAttributeValue("opacity").get_value_or("1")));
+ brushColor.setAlpha(opacityToAlpha(attributes.getAttributeValue("fill-opacity").get_value_or("1")));
+ whiteboardElement->setPenColor(penColor);
+ whiteboardElement->setBrushColor(brushColor);
+ whiteboardElement->setID(attributes.getAttributeValue("id").get_value_or(""));
+ getPayloadInternal()->setElement(whiteboardElement);
+ wbElement = whiteboardElement;
+ }
+ }
+ ++level_;
+ }
+
+ void WhiteboardParser::handleEndElement(const std::string& element, const std::string&) {
+ --level_;
+ if (level_ == 0) {
+ getPayloadInternal()->setData(data_);
+ } else if (level_ == 1) {
+ WhiteboardInsertOperation::ref insertOp = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(operation);
+ if (insertOp) {
+ insertOp->setElement(wbElement);
+ }
+
+ WhiteboardUpdateOperation::ref updateOp = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(operation);
+ if (updateOp) {
+ updateOp->setElement(wbElement);
+ }
+ getPayloadInternal()->setOperation(operation);
+ } else if (level_ == 2) {
+ if (element == "text") {
+ actualIsText = false;
+ }
+ }
+ }
+
+ void WhiteboardParser::handleCharacterData(const std::string& data) {
+ if (level_ == 3 && actualIsText) {
+ WhiteboardTextElement::ref element = boost::dynamic_pointer_cast<WhiteboardTextElement>(getPayloadInternal()->getElement());
+ element->setText(data);
+ }
+ }
+
+ WhiteboardPayload::Type WhiteboardParser::stringToType(const std::string& type) const {
+ if (type == "data") {
+ return WhiteboardPayload::Data;
+ } else if (type == "session-request") {
+ return WhiteboardPayload::SessionRequest;
+ } else if (type == "session-accept") {
+ return WhiteboardPayload::SessionAccept;
+ } else if (type == "session-terminate") {
+ return WhiteboardPayload::SessionTerminate;
+ } else {
+ return WhiteboardPayload::UnknownType;
+ }
+ }
+
+ int WhiteboardParser::opacityToAlpha(std::string opacity) const {
+ int value = 255;
+ if (opacity.find('.') != std::string::npos) {
+ opacity = opacity.substr(opacity.find('.')+1, 2);
+ try {
+ value = boost::lexical_cast<int>(opacity)*255/100;
+ } catch (boost::bad_lexical_cast&) {
+ }
+ }
+ return value;
+ }
+}
diff --git a/Swiften/Parser/PayloadParsers/WhiteboardParser.h b/Swiften/Parser/PayloadParsers/WhiteboardParser.h
new file mode 100644
index 0000000..0368c7c
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/WhiteboardParser.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/WhiteboardPayload.h>
+#include <Swiften/Parser/GenericPayloadParser.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+
+namespace Swift {
+ class WhiteboardParser : public Swift::GenericPayloadParser<WhiteboardPayload> {
+ public:
+ WhiteboardParser();
+
+ virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes);
+ virtual void handleEndElement(const std::string& element, const std::string&);
+ virtual void handleCharacterData(const std::string& data);
+
+ private:
+ WhiteboardPayload::Type stringToType(const std::string& type) const;
+ int opacityToAlpha(std::string opacity) const;
+
+ private:
+ bool actualIsText;
+ int level_;
+ std::string data_;
+ WhiteboardElement::ref wbElement;
+ WhiteboardOperation::ref operation;
+ };
+}
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index e4c2778..64e9eb9 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -72,6 +72,7 @@ sources = [
"PayloadParsers/S5BProxyRequestParser.cpp",
"PayloadParsers/DeliveryReceiptParser.cpp",
"PayloadParsers/DeliveryReceiptRequestParser.cpp",
+ "PayloadParsers/WhiteboardParser.cpp",
"PlatformXMLParserFactory.cpp",
"PresenceParser.cpp",
"SerializingParser.cpp",
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 9996728..bfd1e51 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);
+ };
+}