diff options
Diffstat (limited to 'Swift/Controllers')
179 files changed, 7432 insertions, 879 deletions
diff --git a/Swift/Controllers/AdHocController.cpp b/Swift/Controllers/AdHocController.cpp new file mode 100644 index 0000000..c017120 --- /dev/null +++ b/Swift/Controllers/AdHocController.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010-2014 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <boost/bind.hpp> +#include <Swift/Controllers/AdHocController.h> +#include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> + +namespace Swift { + +AdHocController::AdHocController(AdHocCommandWindowFactory* factory, boost::shared_ptr<OutgoingAdHocCommandSession> command) { + window_ = factory->createAdHocCommandWindow(command); + window_->onClosing.connect(boost::bind(&AdHocController::handleWindowClosed, this)); +} + +AdHocController::~AdHocController() { + window_->onClosing.disconnect(boost::bind(&AdHocController::handleWindowClosed, this)); + delete window_; +} + +void AdHocController::setOnline(bool online) { + window_->setOnline(online); +} + +void AdHocController::handleWindowClosed() { + onDeleting(); +} + +} diff --git a/Swift/Controllers/AdHocController.h b/Swift/Controllers/AdHocController.h new file mode 100644 index 0000000..910756f --- /dev/null +++ b/Swift/Controllers/AdHocController.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2010-2014 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <Swiften/AdHoc/OutgoingAdHocCommandSession.h> + +namespace Swift { + +class AdHocCommandWindowFactory; +class AdHocCommandWindow; + +class AdHocController { +public: + AdHocController(AdHocCommandWindowFactory* factory, boost::shared_ptr<OutgoingAdHocCommandSession> command); + ~AdHocController(); + boost::signal<void ()> onDeleting; + void setOnline(bool online); +private: + void handleWindowClosed(); + AdHocCommandWindow* window_; +}; + +} diff --git a/Swift/Controllers/AdHocManager.cpp b/Swift/Controllers/AdHocManager.cpp index e926138..b179ec6 100644 --- a/Swift/Controllers/AdHocManager.cpp +++ b/Swift/Controllers/AdHocManager.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -16,4 +16,5 @@ #include <Swift/Controllers/UIInterfaces/MainWindow.h> #include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> +#include <Swift/Controllers/UIEvents/RequestAdHocWithJIDUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h> @@ -32,4 +33,12 @@ AdHocManager::AdHocManager(const JID& jid, AdHocCommandWindowFactory* factory, I AdHocManager::~AdHocManager() { uiEventStream_->onUIEvent.disconnect(boost::bind(&AdHocManager::handleUIEvent, this, _1)); + for (size_t i = 0; i < controllers_.size(); ++i) { + controllers_[i]->onDeleting.disconnect(boost::bind(&AdHocManager::removeController, this, controllers_[i])); + } +} + +void AdHocManager::removeController(boost::shared_ptr<AdHocController> controller) { + controller->onDeleting.disconnect(boost::bind(&AdHocManager::removeController, this, controller)); + controllers_.erase(std::find(controllers_.begin(), controllers_.end(), controller)); } @@ -46,5 +55,10 @@ void AdHocManager::setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info) { mainWindow_->setAvailableAdHocCommands(std::vector<DiscoItems::Item>()); } +} +void AdHocManager::setOnline(bool online) { + foreach (boost::shared_ptr<AdHocController> controller, controllers_) { + controller->setOnline(online); + } } @@ -64,5 +78,15 @@ void AdHocManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { boost::shared_ptr<RequestAdHocUIEvent> adHocEvent = boost::dynamic_pointer_cast<RequestAdHocUIEvent>(event); if (adHocEvent) { - factory_->createAdHocCommandWindow(boost::make_shared<OutgoingAdHocCommandSession>(adHocEvent->getCommand().getJID(), adHocEvent->getCommand().getNode(), iqRouter_)); + boost::shared_ptr<OutgoingAdHocCommandSession> command = boost::make_shared<OutgoingAdHocCommandSession>(adHocEvent->getCommand().getJID(), adHocEvent->getCommand().getNode(), iqRouter_); + boost::shared_ptr<AdHocController> controller = boost::make_shared<AdHocController>(factory_, command); + controller->onDeleting.connect(boost::bind(&AdHocManager::removeController, this, controller)); + controllers_.push_back(controller); + } + boost::shared_ptr<RequestAdHocWithJIDUIEvent> adHocJIDEvent = boost::dynamic_pointer_cast<RequestAdHocWithJIDUIEvent>(event); + if (!!adHocJIDEvent) { + boost::shared_ptr<OutgoingAdHocCommandSession> command = boost::make_shared<OutgoingAdHocCommandSession>(adHocJIDEvent->getJID(), adHocJIDEvent->getNode(), iqRouter_); + boost::shared_ptr<AdHocController> controller = boost::make_shared<AdHocController>(factory_, command); + controller->onDeleting.connect(boost::bind(&AdHocManager::removeController, this, controller)); + controllers_.push_back(controller); } } diff --git a/Swift/Controllers/AdHocManager.h b/Swift/Controllers/AdHocManager.h index 47b03cd..b2c34c5 100644 --- a/Swift/Controllers/AdHocManager.h +++ b/Swift/Controllers/AdHocManager.h @@ -1,11 +1,8 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#pragma once - -#include <boost/shared_ptr.hpp> #include <vector> @@ -16,5 +13,7 @@ #include <Swiften/Elements/ErrorPayload.h> #include <Swiften/Disco/GetDiscoItemsRequest.h> +#include <Swiften/Client/Client.h> #include <Swift/Controllers/UIEvents/UIEvent.h> +#include <Swift/Controllers/AdHocController.h> namespace Swift { @@ -27,8 +26,11 @@ namespace Swift { AdHocManager(const JID& jid, AdHocCommandWindowFactory* factory, IQRouter* iqRouter, UIEventStream* uiEventStream, MainWindow* mainWindow); ~AdHocManager(); + void removeController(boost::shared_ptr<AdHocController> contoller); void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info); + void setOnline(bool online); private: - void handleUIEvent(boost::shared_ptr<UIEvent> event); void handleServerDiscoItemsResponse(boost::shared_ptr<DiscoItems>, ErrorPayload::ref error); + void handleUIEvent(boost::shared_ptr<UIEvent> event); + boost::signal<void (const AdHocController&)> onControllerComplete; JID jid_; IQRouter* iqRouter_; @@ -37,4 +39,6 @@ namespace Swift { AdHocCommandWindowFactory* factory_; GetDiscoItemsRequest::ref discoItemsRequest_; + std::vector<boost::shared_ptr<AdHocController> > controllers_; }; + } diff --git a/Swift/Controllers/BlockListController.cpp b/Swift/Controllers/BlockListController.cpp new file mode 100644 index 0000000..9cd42f0 --- /dev/null +++ b/Swift/Controllers/BlockListController.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/BlockListController.h> + +#include <boost/bind.hpp> + +#include <Swiften/Client/ClientBlockListManager.h> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/format.h> +#include <Swift/Controllers/Intl.h> +#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h> +#include <Swift/Controllers/XMPPEvents/ErrorEvent.h> +#include <Swift/Controllers/UIInterfaces/BlockListEditorWidget.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> + +namespace Swift { + +BlockListController::BlockListController(ClientBlockListManager* blockListManager, UIEventStream* uiEventStream, BlockListEditorWidgetFactory* blockListEditorWidgetFactory, EventController* eventController) : blockListManager_(blockListManager), blockListEditorWidgetFactory_(blockListEditorWidgetFactory), blockListEditorWidget_(0), eventController_(eventController), remainingRequests_(0), uiEventStream_(uiEventStream) { + uiEventStream->onUIEvent.connect(boost::bind(&BlockListController::handleUIEvent, this, _1)); + blockListManager_->getBlockList()->onItemAdded.connect(boost::bind(&BlockListController::handleBlockListChanged, this)); + blockListManager_->getBlockList()->onItemRemoved.connect(boost::bind(&BlockListController::handleBlockListChanged, this)); +} + +BlockListController::~BlockListController() { + uiEventStream_->onUIEvent.disconnect(boost::bind(&BlockListController::handleUIEvent, this, _1)); + blockListManager_->getBlockList()->onItemAdded.disconnect(boost::bind(&BlockListController::handleBlockListChanged, this)); + blockListManager_->getBlockList()->onItemRemoved.disconnect(boost::bind(&BlockListController::handleBlockListChanged, this)); +} + +void BlockListController::blockListDifferences(const std::vector<JID> &newBlockList, std::vector<JID> &jidsToUnblock, std::vector<JID> &jidsToBlock) const { + foreach (const JID& jid, blockListBeforeEdit) { + if (std::find(newBlockList.begin(), newBlockList.end(), jid) == newBlockList.end()) { + jidsToUnblock.push_back(jid); + } + } + + foreach (const JID& jid, newBlockList) { + if (std::find(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid) == blockListBeforeEdit.end()) { + jidsToBlock.push_back(jid); + } + } +} + +void BlockListController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) { + // handle UI dialog + boost::shared_ptr<RequestBlockListDialogUIEvent> requestDialogEvent = boost::dynamic_pointer_cast<RequestBlockListDialogUIEvent>(rawEvent); + if (requestDialogEvent != NULL) { + if (blockListEditorWidget_ == NULL) { + blockListEditorWidget_ = blockListEditorWidgetFactory_->createBlockListEditorWidget(); + blockListEditorWidget_->onSetNewBlockList.connect(boost::bind(&BlockListController::handleSetNewBlockList, this, _1)); + } + blockListBeforeEdit = blockListManager_->getBlockList()->getItems(); + blockListEditorWidget_->setCurrentBlockList(blockListBeforeEdit); + blockListEditorWidget_->setError(""); + blockListEditorWidget_->show(); + return; + } + + // handle block state change + boost::shared_ptr<RequestChangeBlockStateUIEvent> changeStateEvent = boost::dynamic_pointer_cast<RequestChangeBlockStateUIEvent>(rawEvent); + if (changeStateEvent != NULL) { + if (changeStateEvent->getBlockState() == RequestChangeBlockStateUIEvent::Blocked) { + GenericRequest<BlockPayload>::ref blockRequest = blockListManager_->createBlockJIDRequest(changeStateEvent->getContact()); + blockRequest->onResponse.connect(boost::bind(&BlockListController::handleBlockResponse, this, blockRequest, _1, _2, std::vector<JID>(1, changeStateEvent->getContact()), false)); + blockRequest->send(); + } else if (changeStateEvent->getBlockState() == RequestChangeBlockStateUIEvent::Unblocked) { + GenericRequest<UnblockPayload>::ref unblockRequest = blockListManager_->createUnblockJIDRequest(changeStateEvent->getContact()); + unblockRequest->onResponse.connect(boost::bind(&BlockListController::handleUnblockResponse, this, unblockRequest, _1, _2, std::vector<JID>(1, changeStateEvent->getContact()), false)); + unblockRequest->send(); + } + return; + } +} + +void BlockListController::handleBlockResponse(GenericRequest<BlockPayload>::ref request, boost::shared_ptr<BlockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor) { + if (error) { + std::string errorMessage; + // FIXME: Handle reporting of list of JIDs in a translatable way. + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Failed to block %1%.")) % jids.at(0).toString()); + if (!error->getText().empty()) { + errorMessage = str(format(QT_TRANSLATE_NOOP("", "%1%: %2%.")) % errorMessage % error->getText()); + } + if (blockListEditorWidget_ && originEditor) { + blockListEditorWidget_->setError(errorMessage); + blockListEditorWidget_->setBusy(false); + } + else { + eventController_->handleIncomingEvent(boost::make_shared<ErrorEvent>(request->getReceiver(), errorMessage)); + } + } + if (originEditor) { + remainingRequests_--; + if (blockListEditorWidget_ && (remainingRequests_ == 0) && !error) { + blockListEditorWidget_->setBusy(false); + blockListEditorWidget_->hide(); + } + } +} + +void BlockListController::handleUnblockResponse(GenericRequest<UnblockPayload>::ref request, boost::shared_ptr<UnblockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor) { + if (error) { + std::string errorMessage; + // FIXME: Handle reporting of list of JIDs in a translatable way. + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Failed to unblock %1%.")) % jids.at(0).toString()); + if (!error->getText().empty()) { + errorMessage = str(format(QT_TRANSLATE_NOOP("", "%1%: %2%.")) % errorMessage % error->getText()); + } + if (blockListEditorWidget_ && originEditor) { + blockListEditorWidget_->setError(errorMessage); + blockListEditorWidget_->setBusy(false); + } + else { + eventController_->handleIncomingEvent(boost::make_shared<ErrorEvent>(request->getReceiver(), errorMessage)); + } + } + if (originEditor) { + remainingRequests_--; + if (blockListEditorWidget_ && (remainingRequests_ == 0) && !error) { + blockListEditorWidget_->setBusy(false); + blockListEditorWidget_->hide(); + } + } +} + +void BlockListController::handleSetNewBlockList(const std::vector<JID> &newBlockList) { + std::vector<JID> jidsToBlock; + std::vector<JID> jidsToUnblock; + + blockListDifferences(newBlockList, jidsToUnblock, jidsToBlock); + + if (!jidsToBlock.empty()) { + remainingRequests_++; + GenericRequest<BlockPayload>::ref blockRequest = blockListManager_->createBlockJIDsRequest(jidsToBlock); + blockRequest->onResponse.connect(boost::bind(&BlockListController::handleBlockResponse, this, blockRequest, _1, _2, jidsToBlock, true)); + blockRequest->send(); + } + if (!jidsToUnblock.empty()) { + remainingRequests_++; + GenericRequest<UnblockPayload>::ref unblockRequest = blockListManager_->createUnblockJIDsRequest(jidsToUnblock); + unblockRequest->onResponse.connect(boost::bind(&BlockListController::handleUnblockResponse, this, unblockRequest, _1, _2, jidsToUnblock, true)); + unblockRequest->send(); + } + if (!jidsToBlock.empty() || !jidsToUnblock.empty()) { + assert(blockListEditorWidget_); + blockListEditorWidget_->setBusy(true); + blockListEditorWidget_->setError(""); + } else { + blockListEditorWidget_->hide(); + } +} + +void BlockListController::handleBlockListChanged() { + if (blockListEditorWidget_) { + std::vector<JID> jidsToBlock; + std::vector<JID> jidsToUnblock; + + blockListDifferences(blockListEditorWidget_->getCurrentBlockList(), jidsToUnblock, jidsToBlock); + blockListBeforeEdit = blockListManager_->getBlockList()->getItems(); + + foreach (const JID& jid, jidsToBlock) { + if (std::find(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid) == blockListBeforeEdit.end()) { + blockListBeforeEdit.push_back(jid); + } + } + + foreach (const JID& jid, jidsToUnblock) { + blockListBeforeEdit.erase(std::remove(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid), blockListBeforeEdit.end()); + } + + blockListEditorWidget_->setCurrentBlockList(blockListBeforeEdit); + } +} + +} diff --git a/Swift/Controllers/BlockListController.h b/Swift/Controllers/BlockListController.h new file mode 100644 index 0000000..99c143c --- /dev/null +++ b/Swift/Controllers/BlockListController.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swiften/Queries/GenericRequest.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h> + +namespace Swift { + +class BlockPayload; +class UnblockPayload; +class ClientBlockListManager; +class EventController; + +class BlockListController { +public: + BlockListController(ClientBlockListManager* blockListManager, UIEventStream* uiEventStream, BlockListEditorWidgetFactory* blockListEditorWidgetFactory, EventController* eventController); + ~BlockListController(); + +private: + void blockListDifferences(const std::vector<JID> &newBlockList, std::vector<JID>& jidsToUnblock, std::vector<JID>& jidsToBlock) const; + + void handleUIEvent(boost::shared_ptr<UIEvent> event); + + void handleBlockResponse(GenericRequest<BlockPayload>::ref, boost::shared_ptr<BlockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor); + void handleUnblockResponse(GenericRequest<UnblockPayload>::ref, boost::shared_ptr<UnblockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor); + + void handleSetNewBlockList(const std::vector<JID>& newBlockList); + + void handleBlockListChanged(); + +private: + ClientBlockListManager* blockListManager_; + BlockListEditorWidgetFactory* blockListEditorWidgetFactory_; + BlockListEditorWidget* blockListEditorWidget_; + EventController* eventController_; + std::vector<JID> blockListBeforeEdit; + int remainingRequests_; + UIEventStream* uiEventStream_; +}; + +} diff --git a/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h b/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h new file mode 100644 index 0000000..2265c3b --- /dev/null +++ b/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/Roster/XMPPRoster.h> +#include <Swiften/Elements/MUCInvitationPayload.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/SettingConstants.h> + +namespace Swift { + class AutoAcceptMUCInviteDecider { + public: + AutoAcceptMUCInviteDecider(const JID& domain, XMPPRoster* roster, SettingsProvider* settings) : domain_(domain), roster_(roster), settings_(settings) { + } + + bool isAutoAcceptedInvite(const JID& from, MUCInvitationPayload::ref invite) { + if (!invite->getIsImpromptu()) { + return false; /* always ask the user for normal MUC invites */ + } + + if (invite->getIsContinuation()) { + return true; + } + + std::string auto_accept_mode = settings_->getSetting(SettingConstants::INVITE_AUTO_ACCEPT_MODE); + if (auto_accept_mode == "no") { + return false; + } else if (auto_accept_mode == "presence") { + return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both; + } else if (auto_accept_mode == "domain") { + return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both || from.getDomain() == domain_; + } else { + assert(false); + return false; + } + } + + private: + JID domain_; + XMPPRoster* roster_; + SettingsProvider* settings_; + }; +} diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 5a18a98..0b34681 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -1,9 +1,9 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swift/Controllers/Chat/ChatController.h" +#include <Swift/Controllers/Chat/ChatController.h> #include <boost/bind.hpp> @@ -11,25 +11,37 @@ #include <stdio.h> -#include <Swift/Controllers/Intl.h> -#include <Swiften/Base/format.h> -#include <Swiften/Base/Algorithm.h> #include <Swiften/Avatars/AvatarManager.h> +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/DateTime.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/format.h> +#include <Swiften/Base/Log.h> #include <Swiften/Chat/ChatStateNotifier.h> #include <Swiften/Chat/ChatStateTracker.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/Client/NickResolver.h> #include <Swiften/Client/StanzaChannel.h> +#include <Swiften/Disco/EntityCapsProvider.h> +#include <Swiften/Elements/DeliveryReceipt.h> +#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/Elements/Idle.h> +#include <Swiften/FileTransfer/FileTransferManager.h> + +#include <Swift/Controllers/Intl.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> -#include <Swiften/Client/NickResolver.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/StatusUtil.h> -#include <Swiften/Disco/EntityCapsProvider.h> -#include <Swiften/Base/foreach.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/SendFileUIEvent.h> -#include <Swiften/Elements/DeliveryReceipt.h> -#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h> +#include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h> +#include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h> +#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h> #include <Swift/Controllers/SettingConstants.h> - -#include <Swiften/Base/Log.h> +#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/Chat/ChatMessageParser.h> namespace Swift { @@ -38,6 +50,6 @@ namespace Swift { * The controller does not gain ownership of the stanzaChannel, nor the factory. */ -ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings) - : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings) { +ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) + : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) { isInMUC_ = isInMUC; lastWasPresence_ = false; @@ -60,4 +72,8 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ theirPresence = contact.isBare() ? presenceOracle->getHighestPriorityPresence(contact.toBare()) : presenceOracle->getLastPresence(contact); } + Idle::ref idle; + if (theirPresence && (idle = theirPresence->getPayload<Idle>())) { + startMessage += str(format(QT_TRANSLATE_NOOP("", ", who has been idle since %1%")) % dateTimeToLocalString(idle->getSince())); + } startMessage += ": " + statusShowTypeToFriendlyName(theirPresence ? theirPresence->getShow() : StatusShow::None); if (theirPresence && !theirPresence->getStatus().empty()) { @@ -67,5 +83,5 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available); startMessage += "."; - chatWindow_->addSystemMessage(startMessage); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection); chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); @@ -74,7 +90,15 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ 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)); + chatWindow_->onBlockUserRequest.connect(boost::bind(&ChatController::handleBlockUserRequest, this)); + chatWindow_->onUnblockUserRequest.connect(boost::bind(&ChatController::handleUnblockUserRequest, this)); + chatWindow_->onInviteToChat.connect(boost::bind(&ChatController::handleInviteToChat, this, _1)); + chatWindow_->onClosed.connect(boost::bind(&ChatController::handleWindowClosed, this)); handleBareJIDCapsChanged(toJID_); settings_->onSettingChanged.connect(boost::bind(&ChatController::handleSettingChanged, this, _1)); + eventStream_->onUIEvent.connect(boost::bind(&ChatController::handleUIEvent, this, _1)); } @@ -86,4 +110,5 @@ void ChatController::handleContactNickChanged(const JID& jid, const std::string& ChatController::~ChatController() { + eventStream_->onUIEvent.disconnect(boost::bind(&ChatController::handleUIEvent, this, _1)); settings_->onSettingChanged.disconnect(boost::bind(&ChatController::handleSettingChanged, this, _1)); nickResolver_->onNickChanged.disconnect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2)); @@ -92,4 +117,8 @@ ChatController::~ChatController() { } +JID ChatController::getBaseJID() { + return isInMUC_ ? toJID_ : ChatControllerBase::getBaseJID(); +} + void ChatController::cancelReplaces() { lastWasPresence_ = false; @@ -109,4 +138,9 @@ void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) { contactSupportsReceipts_ = ChatWindow::No; } + if (FileTransferManager::isSupportedBy(disco)) { + chatWindow_->setFileTransferEnabled(ChatWindow::Yes); + } else { + chatWindow_->setFileTransferEnabled(ChatWindow::No); + } } else { SWIFT_LOG(debug) << "No disco info :(" << std::endl; @@ -130,4 +164,17 @@ void ChatController::setToJID(const JID& jid) { } +void ChatController::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) { + ChatControllerBase::setAvailableServerFeatures(info); + if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) { + boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); + + blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); + blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); + blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); + + handleBlockingStateChanged(); + } +} + bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) { return false; @@ -147,6 +194,8 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me } chatStateTracker_->handleMessageReceived(message); - chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>()); + chatStateNotifier_->receivedMessageFromContact(!!message->getPayload<ChatState>()); + // handle XEP-0184 Message Receipts + // incomming receipts if (boost::shared_ptr<DeliveryReceipt> receipt = message->getPayload<DeliveryReceipt>()) { SWIFT_LOG(debug) << "received receipt for id: " << receipt->getReceivedID() << std::endl; @@ -155,4 +204,11 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me requestedReceipts_.erase(receipt->getReceivedID()); } + // incomming errors in response to send out receipts + } else if (message->getPayload<DeliveryReceiptRequest>() && (message->getType() == Message::Error)) { + if (requestedReceipts_.find(message->getID()) != requestedReceipts_.end()) { + chatWindow_->setMessageReceiptState(requestedReceipts_[message->getID()], ChatWindow::ReceiptFailed); + requestedReceipts_.erase(message->getID()); + } + // incoming receipt requests } else if (message->getPayload<DeliveryReceiptRequest>()) { if (receivingPresenceFromUs_) { @@ -165,6 +221,9 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me } -void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { +void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) { eventController_->handleIncomingEvent(messageEvent); + if (!messageEvent->getConcluded()) { + highlighter_->handleHighlightAction(highlight); + } } @@ -189,20 +248,90 @@ void ChatController::handleSettingChanged(const std::string& settingPath) { void ChatController::checkForDisplayingDisplayReceiptsAlert() { + boost::optional<ChatWindow::AlertID> newDeliverReceiptAlert; if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::No)) { - chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "This chat doesn't support delivery receipts.")); + newDeliverReceiptAlert = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat doesn't support delivery receipts.")); } else if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::Maybe)) { - chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.")); + newDeliverReceiptAlert = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.")); + } else { + if (deliveryReceiptAlert_) { + chatWindow_->removeAlert(*deliveryReceiptAlert_); + deliveryReceiptAlert_.reset(); + } + } + if (newDeliverReceiptAlert) { + if (deliveryReceiptAlert_) { + chatWindow_->removeAlert(*deliveryReceiptAlert_); + } + deliveryReceiptAlert_ = newDeliverReceiptAlert; + } +} + +void ChatController::handleBlockingStateChanged() { + boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); + if (blockList->getState() == BlockList::Available) { + if (isInMUC_ ? blockList->isBlocked(toJID_) : blockList->isBlocked(toJID_.toBare())) { + if (!blockedContactAlert_) { + blockedContactAlert_ = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first.")); + } + chatWindow_->setInputEnabled(false); + chatWindow_->setBlockingState(ChatWindow::IsBlocked); + + // disconnect typing events to prevent chat state notifciations to blocked contacts + chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); + chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + } else { + if (blockedContactAlert_) { + chatWindow_->removeAlert(*blockedContactAlert_); + blockedContactAlert_.reset(); + } + chatWindow_->setInputEnabled(true); + chatWindow_->setBlockingState(ChatWindow::IsUnblocked); + + chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); + chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + } + } +} + +void ChatController::handleBlockUserRequest() { + if (isInMUC_) { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_)); } else { - chatWindow_->cancelAlert(); + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_.toBare())); + } +} + +void ChatController::handleUnblockUserRequest() { + if (isInMUC_) { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_)); + } else { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_.toBare())); + } +} + +void ChatController::handleInviteToChat(const std::vector<JID>& droppedJIDs) { + boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(toJID_.toBare(), droppedJIDs, RequestInviteToMUCUIEvent::Impromptu)); + eventStream_->send(event); +} + +void ChatController::handleWindowClosed() { + onWindowClosed(); +} + +void ChatController::handleUIEvent(boost::shared_ptr<UIEvent> event) { + boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event); + if (inviteEvent && inviteEvent->getRoom() == toJID_.toBare()) { + onConvertToMUC(detachChatWindow(), inviteEvent->getInvites(), inviteEvent->getReason()); } } + void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) { boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>(); if (replace) { eraseIf(unackedStanzas_, PairSecondEquals<boost::shared_ptr<Stanza>, std::string>(myLastMessageUIID_)); - replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time()); + replaceMessage(body, myLastMessageUIID_, true, boost::posix_time::microsec_clock::universal_time(), HighlightAction()); } else { - myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time()); + myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), avatarManager_->getAvatarPath(selfJID_), boost::posix_time::microsec_clock::universal_time(), HighlightAction()); } @@ -252,4 +381,12 @@ void ChatController::handleNewFileTransferController(FileTransferController* 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; @@ -271,5 +408,5 @@ void ChatController::handleFileTransferStart(std::string id, std::string descrip void ChatController::handleFileTransferAccept(std::string id, std::string filename) { - SWIFT_LOG(debug) "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl; + SWIFT_LOG(debug) << "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl; if (ftControllers.find(id) != ftControllers.end()) { ftControllers[id]->accept(filename); @@ -284,4 +421,16 @@ void ChatController::handleSendFileRequest(std::string 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); @@ -303,4 +452,9 @@ std::string ChatController::getStatusChangeString(boost::shared_ptr<Presence> pr } } + Idle::ref idle; + if ((idle = presence->getPayload<Idle>())) { + response += str(format(QT_TRANSLATE_NOOP("", " and has been idle since %1%")) % dateTimeToLocalString(idle->getSince())); + } + if (!response.empty()) { response = str(format(response) % nick); @@ -337,7 +491,7 @@ void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresenc if (newStatusChangeString != lastStatusChangeString_) { if (lastWasPresence_) { - chatWindow_->replaceLastMessage(newStatusChangeString); + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::UpdateTimestamp); } else { - chatWindow_->addPresenceMessage(newStatusChangeString); + chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection); } lastStatusChangeString_ = newStatusChangeString; @@ -350,3 +504,23 @@ boost::optional<boost::posix_time::ptime> ChatController::getMessageTimestamp(bo } +void ChatController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool /* isIncoming */) { + HistoryMessage::Type type; + if (mucRegistry_->isMUC(fromJID.toBare()) || mucRegistry_->isMUC(toJID.toBare())) { + type = HistoryMessage::PrivateMessage; + } + else { + type = HistoryMessage::Chat; + } + + if (historyController_) { + historyController_->addMessage(message, fromJID, toJID, type, timeStamp); + } +} + +ChatWindow* ChatController::detachChatWindow() { + chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); + chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + return ChatControllerBase::detachChatWindow(); +} + } diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index 00d167e..998b437 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -22,16 +22,26 @@ namespace Swift { class FileTransferController; class SettingsProvider; + class HistoryController; + class HighlightManager; + class ClientBlockListManager; + class UIEvent; class ChatController : public ChatControllerBase { public: - ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings); + ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); virtual ~ChatController(); virtual void setToJID(const JID& jid); + virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); 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*/); + virtual ChatWindow* detachChatWindow(); protected: void cancelReplaces(); + JID getBaseJID(); + void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming); private: @@ -41,5 +51,5 @@ namespace Swift { void postSendMessage(const std::string &body, boost::shared_ptr<Stanza> sentStanza); void preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent); - void postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent); + void postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction&); void preSendMessageRequest(boost::shared_ptr<Message>); std::string senderDisplayNameFromMessage(const JID& from); @@ -55,7 +65,21 @@ namespace Swift { void handleSendFileRequest(std::string filename); + void handleWhiteboardSessionAccept(); + void handleWhiteboardSessionCancel(); + void handleWhiteboardWindowShow(); + void handleSettingChanged(const std::string& settingPath); void checkForDisplayingDisplayReceiptsAlert(); + void handleBlockingStateChanged(); + void handleBlockUserRequest(); + void handleUnblockUserRequest(); + + void handleInviteToChat(const std::vector<JID>& droppedJIDs); + + void handleWindowClosed(); + + void handleUIEvent(boost::shared_ptr<UIEvent> event); + private: NickResolver* nickResolver_; @@ -76,4 +100,13 @@ namespace Swift { std::map<std::string, FileTransferController*> ftControllers; SettingsProvider* settings_; + std::string lastWbID_; + + ClientBlockListManager* clientBlockListManager_; + boost::bsignals::scoped_connection blockingOnStateChangedConnection_; + boost::bsignals::scoped_connection blockingOnItemAddedConnection_; + boost::bsignals::scoped_connection blockingOnItemRemovedConnection_; + + boost::optional<ChatWindow::AlertID> deliveryReceiptAlert_; + boost::optional<ChatWindow::AlertID> blockedContactAlert_; }; } diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index 3ff52a6..2c2540c 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -1,9 +1,9 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swift/Controllers/Chat/ChatControllerBase.h" +#include <Swift/Controllers/Chat/ChatControllerBase.h> #include <sstream> @@ -14,8 +14,9 @@ #include <boost/smart_ptr/make_shared.hpp> #include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/numeric/conversion/cast.hpp> #include <boost/algorithm/string.hpp> -#include <Swift/Controllers/Intl.h> #include <Swiften/Base/format.h> +#include <Swiften/Base/Path.h> #include <Swiften/Base/String.h> #include <Swiften/Client/StanzaChannel.h> @@ -24,15 +25,23 @@ #include <Swiften/Elements/MUCUserPayload.h> #include <Swiften/Base/foreach.h> -#include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swiften/Disco/EntityCapsProvider.h> -#include <Swift/Controllers/UIInterfaces/ChatWindow.h> -#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h> #include <Swiften/Avatars/AvatarManager.h> + +#include <Swift/Controllers/Intl.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h> +#include <Swift/Controllers/HighlightManager.h> +#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h> +#include <Swift/Controllers/Chat/ChatMessageParser.h> namespace Swift { -ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider) { +ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) { chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream); chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this)); @@ -40,4 +49,5 @@ ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaCha chatWindow_->onLogCleared.connect(boost::bind(&ChatControllerBase::handleLogCleared, this)); entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1)); + highlighter_ = highlightManager->createHighlighter(); setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable()); createDayChangeTimer(); @@ -45,4 +55,5 @@ ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaCha ChatControllerBase::~ChatControllerBase() { + delete highlighter_; delete chatWindow_; } @@ -52,4 +63,10 @@ void ChatControllerBase::handleLogCleared() { } +ChatWindow* ChatControllerBase::detachChatWindow() { + ChatWindow* chatWindow = chatWindow_; + chatWindow_ = NULL; + return chatWindow; +} + void ChatControllerBase::handleCapsChanged(const JID& jid) { if (jid.compare(toJID_, JID::WithoutResource) == 0) { @@ -58,9 +75,15 @@ void ChatControllerBase::handleCapsChanged(const JID& jid) { } +void ChatControllerBase::setCanStartImpromptuChats(bool supportsImpromptu) { + if (chatWindow_) { + chatWindow_->setCanInitiateImpromptuChats(supportsImpromptu); + } +} + void ChatControllerBase::createDayChangeTimer() { if (timerFactory_) { boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); boost::posix_time::ptime midnight(now.date() + boost::gregorian::days(1)); - long millisecondsUntilMidnight = (midnight - now).total_milliseconds(); + int millisecondsUntilMidnight = boost::numeric_cast<int>((midnight - now).total_milliseconds()); dateChangeTimer_ = boost::shared_ptr<Timer>(timerFactory_->createTimer(millisecondsUntilMidnight)); dateChangeTimer_->onTick.connect(boost::bind(&ChatControllerBase::handleDayChangeTick, this)); @@ -72,5 +95,5 @@ void ChatControllerBase::handleDayChangeTick() { dateChangeTimer_->stop(); boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))), ChatWindow::DefaultDirection); dayTicked(); createDayChangeTimer(); @@ -85,7 +108,11 @@ void ChatControllerBase::setOnline(bool online) { } +JID ChatControllerBase::getBaseJID() { + return JID(toJID_.toBare()); +} + void ChatControllerBase::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) { if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::SecurityLabelsCatalogFeature)) { - GetSecurityLabelsCatalogRequest::ref request = GetSecurityLabelsCatalogRequest::create(JID(toJID_.toBare()), iqRouter_); + GetSecurityLabelsCatalogRequest::ref request = GetSecurityLabelsCatalogRequest::create(getBaseJID(), iqRouter_); request->onResponse.connect(boost::bind(&ChatControllerBase::handleSecurityLabelsCatalogResponse, this, _1, _2)); request->send(); @@ -109,5 +136,5 @@ void ChatControllerBase::handleAllMessagesRead() { int ChatControllerBase::getUnreadCount() { - return targetedUnreadMessages_.size(); + return boost::numeric_cast<int>(targetedUnreadMessages_.size()); } @@ -121,5 +148,8 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool message->setBody(body); if (labelsEnabled_) { - SecurityLabelsCatalog::Item labelItem = chatWindow_->getSelectedSecurityLabel(); + if (!isCorrectionMessage) { + lastLabel_ = chatWindow_->getSelectedSecurityLabel(); + } + SecurityLabelsCatalog::Item labelItem = lastLabel_; if (labelItem.getLabel()) { message->addPayload(labelItem.getLabel()); @@ -127,6 +157,7 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool } preSendMessageRequest(message); - if (useDelayForLatency_) { + boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + if (useDelayForLatency_) { message->addPayload(boost::make_shared<Delay>(now, selfJID_)); } @@ -138,8 +169,12 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool postSendMessage(message->getBody(), boost::dynamic_pointer_cast<Stanza>(message)); onActivity(message->getBody()); + +#ifdef SWIFT_EXPERIMENTAL_HISTORY + logMessage(body, selfJID_, toJID_, now, false); +#endif } void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) { - if (!error) { + if (catalog && !error) { if (catalog->getItems().size() == 0) { chatWindow_->setSecurityLabelsEnabled(false); @@ -164,17 +199,21 @@ void ChatControllerBase::activateChatWindow() { } -std::string ChatControllerBase::addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) { +bool ChatControllerBase::hasOpenWindow() const { + return chatWindow_ && chatWindow_->isVisible(); +} + +std::string ChatControllerBase::addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) { if (boost::starts_with(message, "/me ")) { - return chatWindow_->addAction(String::getSplittedAtFirst(message, ' ').second, senderName, senderIsSelf, label, avatarPath, time); + return chatWindow_->addAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight); } else { - return chatWindow_->addMessage(message, senderName, senderIsSelf, label, avatarPath, time); + return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message,highlighter_->getNick(),senderIsSelf), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight); } } -void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) { +void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, bool senderIsSelf, const boost::posix_time::ptime& time, const HighlightAction& highlight) { if (boost::starts_with(message, "/me ")) { - chatWindow_->replaceWithAction(String::getSplittedAtFirst(message, ' ').second, id, time); + chatWindow_->replaceWithAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), id, time, highlight); } else { - chatWindow_->replaceMessage(message, id, time); + chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message,highlighter_->getNick(),senderIsSelf), id, time, highlight); } } @@ -194,7 +233,10 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m boost::shared_ptr<Message> message = messageEvent->getStanza(); std::string body = message->getBody(); + HighlightAction highlight; if (message->isError()) { + if (!message->getTo().getResource().empty()) { std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>())); - chatWindow_->addErrorMessage(errorMessage); + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); + } } else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) { @@ -220,5 +262,5 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m std::ostringstream s; s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 << " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << "."; - chatWindow_->addSystemMessage(std::string(s.str())); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection); } boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>(); @@ -232,4 +274,9 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m onActivity(body); + // Highlight + if (!isIncomingMessageFromMe(message)) { + highlight = highlighter_->findAction(body, senderDisplayNameFromMessage(from)); + } + boost::shared_ptr<Replace> replace = message->getPayload<Replace>(); if (replace) { @@ -239,15 +286,17 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m lastMessage = lastMessagesUIID_.find(from); if (lastMessage != lastMessagesUIID_.end()) { - replaceMessage(body, lastMessagesUIID_[from], timeStamp); + replaceMessage(body, lastMessagesUIID_[from], isIncomingMessageFromMe(message), timeStamp, highlight); } } else { - lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp); + lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, avatarManager_->getAvatarPath(from), timeStamp, highlight); } + + logMessage(body, from, selfJID_, timeStamp, true); } chatWindow_->show(); - chatWindow_->setUnreadMessageCount(unreadMessages_.size()); + chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size())); onUnreadCountChanged(); - postHandleIncomingMessage(messageEvent); + postHandleIncomingMessage(messageEvent, highlight); } @@ -259,28 +308,29 @@ std::string ChatControllerBase::getErrorMessage(boost::shared_ptr<ErrorPayload> else { switch (error->getCondition()) { - case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request"); break; - case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict"); break; - case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented"); break; - case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden"); break; - case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted"); break; - case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error"); break; - case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found"); break; - case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed"); break; - case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected"); break; - case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed"); break; - case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized"); break; - case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required"); break; - case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable"); break; - case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect"); break; - case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required"); break; - case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found"); break; - case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout"); break; - case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources"); break; - case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable"); break; - case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required"); break; - case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition"); break; - case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request"); break; - } - } + case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request"); + case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict"); + case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented"); + case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden"); + case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted"); + case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error"); + case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found"); + case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed"); + case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected"); + case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed"); + case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized"); + case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required"); + case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable"); + case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect"); + case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required"); + case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found"); + case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout"); + case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources"); + case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable"); + case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required"); + case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition"); + case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request"); + } + } + assert(false); return defaultMessage; } @@ -289,7 +339,7 @@ void ChatControllerBase::handleGeneralMUCInvitation(MUCInviteEvent::ref event) { unreadMessages_.push_back(event); chatWindow_->show(); - chatWindow_->setUnreadMessageCount(unreadMessages_.size()); + chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size())); onUnreadCountChanged(); - chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect()); + chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect(), event->getImpromptu()); eventController_->handleIncomingEvent(event); } @@ -298,7 +348,11 @@ void ChatControllerBase::handleMUCInvitation(Message::ref message) { MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>(); - MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true); + if (autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) { + eventStream_->send(boost::make_shared<JoinMUCUIEvent>(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true)); + } else { + MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true, invite->getIsImpromptu()); handleGeneralMUCInvitation(inviteEvent); } +} void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) { @@ -314,9 +368,7 @@ void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) { } - MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false); + MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false, false); handleGeneralMUCInvitation(inviteEvent); } - - } diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index 8654311..a0b848b 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -9,28 +9,33 @@ #include <map> #include <vector> +#include <string> + #include <boost/shared_ptr.hpp> -#include "Swiften/Base/boost_bsignals.h" -#include <boost/filesystem.hpp> +#include <boost/filesystem/path.hpp> #include <boost/optional.hpp> #include <boost/date_time/posix_time/posix_time.hpp> -#include "Swiften/Network/Timer.h" -#include "Swiften/Network/TimerFactory.h" -#include "Swiften/Elements/Stanza.h" -#include <string> -#include "Swiften/Elements/DiscoInfo.h" -#include "Swift/Controllers/XMPPEvents/MessageEvent.h" +#include <Swiften/Network/Timer.h> +#include <Swiften/Network/TimerFactory.h> +#include <Swiften/Elements/Stanza.h> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/SecurityLabelsCatalog.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/Base/IDGenerator.h> +#include <Swiften/MUC/MUCRegistry.h> + +#include <Swift/Controllers/XMPPEvents/MessageEvent.h> #include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h> -#include "Swiften/JID/JID.h" -#include "Swiften/Elements/SecurityLabelsCatalog.h" -#include "Swiften/Elements/ErrorPayload.h" -#include "Swiften/Presence/PresenceOracle.h" -#include "Swiften/Queries/IQRouter.h" -#include "Swiften/Base/IDGenerator.h" +#include <Swift/Controllers/HistoryController.h> +#include <Swift/Controllers/HighlightManager.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { class IQRouter; class StanzaChannel; - class ChatWindow; class ChatWindowFactory; class AvatarManager; @@ -38,4 +43,8 @@ namespace Swift { class EventController; class EntityCapsProvider; + class HighlightManager; + class Highlighter; + class ChatMessageParser; + class AutoAcceptMUCInviteDecider; class ChatControllerBase : public boost::bsignals::trackable { @@ -44,37 +53,45 @@ namespace Swift { void showChatWindow(); void activateChatWindow(); - void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); + bool hasOpenWindow() const; + virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); void handleIncomingMessage(boost::shared_ptr<MessageEvent> message); - std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time); - void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time); + std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight); + void replaceMessage(const std::string& message, const std::string& id, bool senderIsSelf, const boost::posix_time::ptime& time, const HighlightAction& highlight); virtual void setOnline(bool online); virtual void setEnabled(bool enabled); - virtual void setToJID(const JID& jid) {toJID_ = jid;}; + virtual void setToJID(const JID& jid) {toJID_ = jid;} /** Used for determining when something is recent.*/ boost::signal<void (const std::string& /*activity*/)> onActivity; boost::signal<void ()> onUnreadCountChanged; + boost::signal<void ()> onWindowClosed; int getUnreadCount(); const JID& getToJID() {return toJID_;} void handleCapsChanged(const JID& jid); + void setCanStartImpromptuChats(bool supportsImpromptu); + virtual ChatWindow* detachChatWindow(); + boost::signal<void(ChatWindow* /*window to reuse*/, const std::vector<JID>& /*invite people*/, const std::string& /*reason*/)> onConvertToMUC; protected: - ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider); + ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); /** * Pass the Message appended, and the stanza used to send it. */ - virtual void postSendMessage(const std::string&, boost::shared_ptr<Stanza>) {}; + virtual void postSendMessage(const std::string&, boost::shared_ptr<Stanza>) {} virtual std::string senderDisplayNameFromMessage(const JID& from) = 0; virtual bool isIncomingMessageFromMe(boost::shared_ptr<Message>) = 0; - virtual void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {}; - virtual void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {}; - virtual void preSendMessageRequest(boost::shared_ptr<Message>) {}; + virtual void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {} + virtual void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>, const HighlightAction&) {} + virtual void preSendMessageRequest(boost::shared_ptr<Message>) {} virtual bool isFromContact(const JID& from); virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const = 0; - virtual void dayTicked() {}; + virtual void dayTicked() {} virtual void handleBareJIDCapsChanged(const JID& jid) = 0; std::string getErrorMessage(boost::shared_ptr<ErrorPayload>); virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {} virtual void cancelReplaces() = 0; + /** JID any iq for account should go to - bare except for PMs */ + virtual JID getBaseJID(); + virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0; private: @@ -109,4 +126,11 @@ namespace Swift { TimerFactory* timerFactory_; EntityCapsProvider* entityCapsProvider_; + SecurityLabelsCatalog::Item lastLabel_; + HistoryController* historyController_; + MUCRegistry* mucRegistry_; + Highlighter* highlighter_; + boost::shared_ptr<ChatMessageParser> chatMessageParser_; + AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; + UIEventStream* eventStream_; }; } diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp new file mode 100644 index 0000000..5a608db --- /dev/null +++ b/Swift/Controllers/Chat/ChatMessageParser.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2013-2014 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/Chat/ChatMessageParser.h> + +#include <vector> +#include <utility> + +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/algorithm/string.hpp> + +#include <Swiften/Base/Regex.h> +#include <Swiften/Base/foreach.h> + +#include <SwifTools/Linkify.h> + + +namespace Swift { + + ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode) + : emoticons_(emoticons), highlightRules_(highlightRules), mucMode_(mucMode) { + } + + typedef std::pair<std::string, std::string> StringPair; + + ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, const std::string& nick, bool senderIsSelf) { + ChatWindow::ChatMessage parsedMessage; + std::string remaining = body; + /* Parse one, URLs */ + while (!remaining.empty()) { + bool found = false; + std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining); + remaining = ""; + for (size_t i = 0; i < links.first.size(); i++) { + const std::string& part = links.first[i]; + if (found) { + // Must be on the last part, then + remaining = part; + } + else { + if (i == links.second) { + found = true; + parsedMessage.append(boost::make_shared<ChatWindow::ChatURIMessagePart>(part)); + } + else { + parsedMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(part)); + } + } + } + } + + /* do emoticon substitution */ + parsedMessage = emoticonHighlight(parsedMessage); + + if (!senderIsSelf) { /* do not highlight our own messsages */ + /* do word-based color highlighting */ + parsedMessage = splitHighlight(parsedMessage, nick); + } + + return parsedMessage; + } + + ChatWindow::ChatMessage ChatMessageParser::emoticonHighlight(const ChatWindow::ChatMessage& message) + { + ChatWindow::ChatMessage parsedMessage = message; + + std::string regexString; + /* Parse two, emoticons */ + foreach (StringPair emoticon, emoticons_) { + /* Construct a regexp that finds an instance of any of the emoticons inside a group + * at the start or end of the line, or beside whitespace. + */ + regexString += regexString.empty() ? "" : "|"; + std::string escaped = "(" + Regex::escape(emoticon.first) + ")"; + regexString += "^" + escaped + "|"; + regexString += escaped + "$|"; + regexString += "\\s" + escaped + "|"; + regexString += escaped + "\\s"; + + } + if (!regexString.empty()) { + regexString += ""; + boost::regex emoticonRegex(regexString); + + ChatWindow::ChatMessage newMessage; + foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) { + boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; + if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { + try { + boost::match_results<std::string::const_iterator> match; + const std::string& text = textPart->text; + std::string::const_iterator start = text.begin(); + while (regex_search(start, text.end(), match, emoticonRegex)) { + int matchIndex = 0; + for (matchIndex = 1; matchIndex < static_cast<int>(match.size()); matchIndex++) { + if (match[matchIndex].length() > 0) { + //This is the matching subgroup + break; + } + } + std::string::const_iterator matchStart = match[matchIndex].first; + std::string::const_iterator matchEnd = match[matchIndex].second; + if (start != matchStart) { + /* If we're skipping over plain text since the previous emoticon, record it as plain text */ + newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); + } + boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = boost::make_shared<ChatWindow::ChatEmoticonMessagePart>(); + std::string matchString = match[matchIndex].str(); + std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(matchString); + assert (emoticonIterator != emoticons_.end()); + const StringPair& emoticon = *emoticonIterator; + emoticonPart->imagePath = emoticon.second; + emoticonPart->alternativeText = emoticon.first; + newMessage.append(emoticonPart); + start = matchEnd; + } + if (start != text.end()) { + /* If there's plain text after the last emoticon, record it */ + newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); + } + + } + catch (std::runtime_error) { + /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ + newMessage.append(part); + } + } + else { + newMessage.append(part); + } + } + parsedMessage = newMessage; + + } + return parsedMessage; + } + + ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message, const std::string& nick) + { + ChatWindow::ChatMessage parsedMessage = message; + + for (size_t i = 0; i < highlightRules_->getSize(); ++i) { + const HighlightRule& rule = highlightRules_->getRule(i); + if (rule.getMatchMUC() && !mucMode_) { + continue; /* this rule only applies to MUC's, and this is a CHAT */ + } else if (rule.getMatchChat() && mucMode_) { + continue; /* this rule only applies to CHAT's, and this is a MUC */ + } + const std::vector<boost::regex> keywordRegex = rule.getKeywordRegex(nick); + foreach(const boost::regex& regex, keywordRegex) { + ChatWindow::ChatMessage newMessage; + foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) { + boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; + if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { + try { + boost::match_results<std::string::const_iterator> match; + const std::string& text = textPart->text; + std::string::const_iterator start = text.begin(); + while (regex_search(start, text.end(), match, regex)) { + std::string::const_iterator matchStart = match[0].first; + std::string::const_iterator matchEnd = match[0].second; + if (start != matchStart) { + /* If we're skipping over plain text since the previous emoticon, record it as plain text */ + newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); + } + boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = boost::make_shared<ChatWindow::ChatHighlightingMessagePart>(); + highlightPart->text = match.str(); + highlightPart->foregroundColor = rule.getAction().getTextColor(); + highlightPart->backgroundColor = rule.getAction().getTextBackground(); + newMessage.append(highlightPart); + start = matchEnd; + } + if (start != text.end()) { + /* If there's plain text after the last emoticon, record it */ + newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); + } + } + catch (std::runtime_error) { + /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ + newMessage.append(part); + } + } else { + newMessage.append(part); + } + } + parsedMessage = newMessage; + } + } + + return parsedMessage; + } +} diff --git a/Swift/Controllers/Chat/ChatMessageParser.h b/Swift/Controllers/Chat/ChatMessageParser.h new file mode 100644 index 0000000..2f5c171 --- /dev/null +++ b/Swift/Controllers/Chat/ChatMessageParser.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013-2014 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> + +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> + +namespace Swift { + + class ChatMessageParser { + public: + ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode = false); + ChatWindow::ChatMessage parseMessageBody(const std::string& body, const std::string& nick = "", bool senderIsSelf = false); + private: + ChatWindow::ChatMessage emoticonHighlight(const ChatWindow::ChatMessage& parsedMessage); + ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& nick); + std::map<std::string, std::string> emoticons_; + HighlightRulesListPtr highlightRules_; + bool mucMode_; + }; +} diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index b0aef95..979f87a 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -1,21 +1,45 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swift/Controllers/Chat/ChatsManager.h" +#include <Swift/Controllers/Chat/ChatsManager.h> #include <boost/bind.hpp> #include <boost/algorithm/string.hpp> #include <boost/smart_ptr/make_shared.hpp> +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.hpp> +#include <boost/serialization/optional.hpp> +#include <boost/serialization/vector.hpp> +#include <boost/serialization/map.hpp> +#include <boost/serialization/string.hpp> +#include <boost/serialization/split_free.hpp> #include <Swiften/Base/foreach.h> +#include <Swiften/Presence/PresenceSender.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/MUC/MUCManager.h> +#include <Swiften/Elements/ChatState.h> +#include <Swiften/Elements/MUCUserPayload.h> +#include <Swiften/Elements/DeliveryReceipt.h> +#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/MUC/MUCBookmarkManager.h> +#include <Swiften/Avatars/AvatarManager.h> +#include <Swiften/Elements/MUCInvitationPayload.h> +#include <Swiften/Roster/XMPPRoster.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/Client/StanzaChannel.h> +#include <Swiften/VCards/VCardManager.h> + #include <Swift/Controllers/Chat/ChatController.h> #include <Swift/Controllers/Chat/ChatControllerBase.h> #include <Swift/Controllers/Chat/MUCSearchController.h> +#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swift/Controllers/Chat/MUCController.h> #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> @@ -23,24 +47,53 @@ #include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h> #include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h> +#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> #include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h> #include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h> -#include <Swiften/Presence/PresenceSender.h> -#include <Swiften/Client/NickResolver.h> -#include <Swiften/MUC/MUCManager.h> -#include <Swiften/Elements/ChatState.h> -#include <Swiften/Elements/MUCUserPayload.h> -#include <Swiften/Elements/DeliveryReceipt.h> -#include <Swiften/Elements/DeliveryReceiptRequest.h> -#include <Swiften/MUC/MUCBookmarkManager.h> #include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/FileTransfer/FileTransferOverview.h> #include <Swift/Controllers/ProfileSettingsProvider.h> -#include <Swiften/Avatars/AvatarManager.h> -#include <Swiften/Elements/MUCInvitationPayload.h> -#include <Swiften/Roster/XMPPRoster.h> #include <Swift/Controllers/Settings/SettingsProvider.h> #include <Swift/Controllers/SettingConstants.h> -#include <Swiften/Client/StanzaChannel.h> +#include <Swift/Controllers/WhiteboardManager.h> +#include <Swift/Controllers/Chat/ChatMessageParser.h> +#include <Swift/Controllers/Chat/UserSearchController.h> +#include <Swiften/Disco/DiscoServiceWalker.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/StringCodecs/Base64.h> +#include <Swiften/Base/Log.h> + +BOOST_CLASS_VERSION(Swift::ChatListWindow::Chat, 1) + +namespace boost { +namespace serialization { + template<class Archive> void save(Archive& ar, const Swift::JID& jid, const unsigned int /*version*/) { + std::string jidStr = jid.toString(); + ar << jidStr; + } + + template<class Archive> void load(Archive& ar, Swift::JID& jid, const unsigned int /*version*/) { + std::string stringJID; + ar >> stringJID; + jid = Swift::JID(stringJID); + } + + template<class Archive> inline void serialize(Archive& ar, Swift::JID& t, const unsigned int file_version){ + split_free(ar, t, file_version); + } + + template<class Archive> void serialize(Archive& ar, Swift::ChatListWindow::Chat& chat, const unsigned int version) { + ar & chat.jid; + ar & chat.chatName; + ar & chat.activity; + ar & chat.isMUC; + ar & chat.nick; + ar & chat.impromptuJIDs; + if (version > 0) { + ar & chat.password; + } + } +} +} namespace Swift { @@ -72,5 +125,12 @@ ChatsManager::ChatsManager( XMPPRoster* roster, bool eagleMode, - SettingsProvider* settings) : + SettingsProvider* settings, + HistoryController* historyController, + WhiteboardManager* whiteboardManager, + HighlightManager* highlightManager, + ClientBlockListManager* clientBlockListManager, + const std::map<std::string, std::string>& emoticons, + UserSearchController* inviteUserSearchController, + VCardManager* vcardManager) : jid_(jid), joinMUCWindowFactory_(joinMUCWindowFactory), @@ -82,5 +142,12 @@ ChatsManager::ChatsManager( roster_(roster), eagleMode_(eagleMode), - settings_(settings) { + settings_(settings), + historyController_(historyController), + whiteboardManager_(whiteboardManager), + highlightManager_(highlightManager), + emoticons_(emoticons), + clientBlockListManager_(clientBlockListManager), + inviteUserSearchController_(inviteUserSearchController), + vcardManager_(vcardManager) { timerFactory_ = timerFactory; eventController_ = eventController; @@ -108,4 +175,8 @@ ChatsManager::ChatsManager( 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)); @@ -119,4 +190,6 @@ ChatsManager::ChatsManager( setupBookmarks(); loadRecents(); + + autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(jid.getDomain(), roster_, settings_); } @@ -136,23 +209,32 @@ ChatsManager::~ChatsManager() { delete mucBookmarkManager_; delete mucSearchController_; + delete autoAcceptMUCInviteDecider_; } void ChatsManager::saveRecents() { - std::string recents; - int i = 1; - foreach (ChatListWindow::Chat chat, recentChats_) { - std::vector<std::string> activity; - boost::split(activity, chat.activity, boost::is_any_of("\t\n")); - if (activity.empty()) { - /* Work around Boost bug https://svn.boost.org/trac/boost/ticket/4751 */ - activity.push_back(""); - } - std::string recent = chat.jid.toString() + "\t" + (eagleMode_ ? "" : activity[0]) + "\t" + (chat.isMUC ? "true" : "false") + "\t" + chat.nick; - recents += recent + "\n"; - if (i++ > 25) { - break; + std::stringstream serializeStream; + boost::archive::text_oarchive oa(serializeStream); + std::vector<ChatListWindow::Chat> recentsLimited = std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end()); + if (recentsLimited.size() > 25) { + recentsLimited.erase(recentsLimited.begin() + 25, recentsLimited.end()); } + if (eagleMode_) { + foreach(ChatListWindow::Chat& chat, recentsLimited) { + chat.activity = ""; } - profileSettings_->storeString(RECENT_CHATS, recents); + } + + class RemoveRecent { + public: + static bool ifPrivateMessage(const ChatListWindow::Chat& chat) { + return chat.isPrivateMessage; + } + }; + + recentsLimited.erase(std::remove_if(recentsLimited.begin(), recentsLimited.end(), RemoveRecent::ifPrivateMessage), recentsLimited.end()); + + oa << recentsLimited; + std::string serializedStr = Base64::encode(createByteArray(serializeStream.str())); + profileSettings_->storeString(RECENT_CHATS, serializedStr); } @@ -196,6 +278,24 @@ void ChatsManager::updatePresenceReceivingStateOnChatController(const JID &jid) } +ChatListWindow::Chat ChatsManager::updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const { + ChatListWindow::Chat fixedChat = chat; + if (fixedChat.isMUC) { + if (mucControllers_.find(fixedChat.jid.toBare()) != mucControllers_.end()) { + fixedChat.statusType = StatusShow::Online; + } + } else { + if (avatarManager_) { + fixedChat.avatarPath = avatarManager_->getAvatarPath(fixedChat.jid); + } + Presence::ref presence = presenceOracle_->getHighestPriorityPresence(fixedChat.jid.toBare()); + fixedChat.statusType = presence ? presence->getShow() : StatusShow::None; + } + return fixedChat; +} + void ChatsManager::loadRecents() { std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS)); + if (recentsString.find("\t") != std::string::npos) { + // old format std::vector<std::string> recents; boost::split(recents, recentsString, boost::is_any_of("\n")); @@ -219,19 +319,28 @@ void ChatsManager::loadRecents() { StatusShow::Type type = StatusShow::None; boost::filesystem::path path; - if (isMUC) { - if (mucControllers_.find(jid.toBare()) != mucControllers_.end()) { - type = StatusShow::Online; - } - } else { - if (avatarManager_) { - path = avatarManager_->getAvatarPath(jid); + + ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, false, nick); + chat = updateChatStatusAndAvatarHelper(chat); + prependRecent(chat); } - Presence::ref presence = presenceOracle_->getHighestPriorityPresence(jid.toBare()); - type = presence ? presence->getShow() : StatusShow::None; + } else if (!recentsString.empty()){ + // boost searilaize based format + ByteArray debase64 = Base64::decode(recentsString); + std::vector<ChatListWindow::Chat> recentChats; + std::stringstream deserializeStream(std::string(reinterpret_cast<const char*>(vecptr(debase64)), debase64.size())); + try { + boost::archive::text_iarchive ia(deserializeStream); + ia >> recentChats; + } catch (const boost::archive::archive_exception& e) { + SWIFT_LOG(debug) << "Failed to load recents: " << e.what() << std::endl; + return; } - ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, nick); + foreach(ChatListWindow::Chat chat, recentChats) { + chat.statusType = StatusShow::None; + chat = updateChatStatusAndAvatarHelper(chat); prependRecent(chat); } + } handleUnreadCountChanged(NULL); } @@ -260,5 +369,5 @@ void ChatsManager::handleMUCBookmarkAdded(const MUCBookmark& bookmark) { std::map<JID, MUCController*>::iterator it = mucControllers_.find(bookmark.getRoom()); if (it == mucControllers_.end() && bookmark.getAutojoin()) { - handleJoinMUCRequest(bookmark.getRoom(), bookmark.getPassword(), bookmark.getNick(), false, false); + handleJoinMUCRequest(bookmark.getRoom(), bookmark.getPassword(), bookmark.getNick(), false, false, false ); } chatListWindow_->addMUCBookmark(bookmark); @@ -269,5 +378,5 @@ void ChatsManager::handleMUCBookmarkRemoved(const MUCBookmark& bookmark) { } -ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity) { +ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity, bool privateMessage) { int unreadCount = 0; if (mucRegistry_->isMUC(jid)) { @@ -275,4 +384,5 @@ ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const StatusShow::Type type = StatusShow::None; std::string nick = ""; + std::string password = ""; if (controller) { unreadCount = controller->getUnreadCount(); @@ -281,7 +391,17 @@ ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const } nick = controller->getNick(); + + if (controller->getPassword()) { + password = *controller->getPassword(); } - return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick); + if (controller->isImpromptu()) { + ChatListWindow::Chat chat = ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, privateMessage, nick, password); + std::map<std::string, JID> participants = controller->getParticipantJIDs(); + chat.impromptuJIDs = participants; + return chat; + } + } + return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, privateMessage, nick, password); } else { ChatController* controller = getChatControllerIfExists(jid, false); @@ -293,14 +413,11 @@ ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const StatusShow::Type type = presence ? presence->getShow() : StatusShow::None; boost::filesystem::path avatarPath = avatarManager_ ? avatarManager_->getAvatarPath(bareishJID) : boost::filesystem::path(); - return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false); + return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false, privateMessage); } } void ChatsManager::handleChatActivity(const JID& jid, const std::string& activity, bool isMUC) { - if (mucRegistry_->isMUC(jid.toBare()) && !isMUC) { - /* Don't include PMs in MUC rooms.*/ - return; - } - ChatListWindow::Chat chat = createChatListChatItem(jid, activity); + const bool privateMessage = mucRegistry_->isMUC(jid.toBare()) && !isMUC; + ChatListWindow::Chat chat = createChatListChatItem(jid, activity, privateMessage); /* FIXME: handle nick changes */ appendRecent(chat); @@ -309,4 +426,9 @@ void ChatsManager::handleChatActivity(const JID& jid, const std::string& activit } +void ChatsManager::handleChatClosed(const JID& /*jid*/) { + cleanupPrivateMessageRecents(); + chatListWindow_->setRecents(recentChats_); +} + void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) { int unreadTotal = 0; @@ -332,12 +454,49 @@ void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) { } -void ChatsManager::appendRecent(const ChatListWindow::Chat& chat) { +boost::optional<ChatListWindow::Chat> ChatsManager::removeExistingChat(const ChatListWindow::Chat& chat) { + std::list<ChatListWindow::Chat>::iterator result = std::find(recentChats_.begin(), recentChats_.end(), chat); + if (result != recentChats_.end()) { + ChatListWindow::Chat existingChat = *result; recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end()); - recentChats_.push_front(chat); + return boost::optional<ChatListWindow::Chat>(existingChat); + } else { + return boost::optional<ChatListWindow::Chat>(); + } +} + +void ChatsManager::cleanupPrivateMessageRecents() { + /* if we leave a MUC and close a PM, remove it's recent chat entry */ + const std::list<ChatListWindow::Chat> chats = recentChats_; + foreach (const ChatListWindow::Chat& chat, chats) { + if (chat.isPrivateMessage) { + typedef std::map<JID, MUCController*> ControllerMap; + ControllerMap::iterator muc = mucControllers_.find(chat.jid.toBare()); + if (muc == mucControllers_.end() || !muc->second->isJoined()) { + ChatController* chatController = getChatControllerIfExists(chat.jid); + if (!chatController || !chatController->hasOpenWindow()) { + removeExistingChat(chat); + break; + } + } + } + } +} + +void ChatsManager::appendRecent(const ChatListWindow::Chat& chat) { + boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat); + ChatListWindow::Chat mergedChat = chat; + if (oldChat && !oldChat->impromptuJIDs.empty()) { + mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end()); + } + recentChats_.push_front(mergedChat); } void ChatsManager::prependRecent(const ChatListWindow::Chat& chat) { - recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end()); - recentChats_.push_back(chat); + boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat); + ChatListWindow::Chat mergedChat = chat; + if (oldChat && !oldChat->impromptuJIDs.empty()) { + mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end()); + } + recentChats_.push_back(mergedChat); } @@ -349,13 +508,13 @@ void ChatsManager::handleUserLeftMUC(MUCController* mucController) { if (chat.isMUC && chat.jid == (*it).first) { chat.statusType = StatusShow::None; - chatListWindow_->setRecents(recentChats_); - break; } } mucControllers_.erase(it); delete mucController; - return; + break; } } + cleanupPrivateMessageRecents(); + chatListWindow_->setRecents(recentChats_); } @@ -367,4 +526,25 @@ void ChatsManager::handleSettingChanged(const std::string& settingPath) { } +void ChatsManager::finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID) { + // send impromptu invites for the new MUC + std::vector<JID> missingJIDsToInvite = jidsToInvite; + + typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair; + std::map<std::string, MUCOccupant> occupants = muc->getOccupants(); + foreach(StringMUCOccupantPair occupant, occupants) { + boost::optional<JID> realJID = occupant.second.getRealJID(); + if (realJID) { + missingJIDsToInvite.erase(std::remove(missingJIDsToInvite.begin(), missingJIDsToInvite.end(), realJID->toBare()), missingJIDsToInvite.end()); + } + } + + if (reuseChatJID) { + muc->invitePerson(reuseChatJID.get(), reason, true, true); + } + foreach(const JID& jid, missingJIDsToInvite) { + muc->invitePerson(jid, reason, true); + } +} + void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { boost::shared_ptr<RequestChatUIEvent> chatEvent = boost::dynamic_pointer_cast<RequestChatUIEvent>(event); @@ -384,4 +564,15 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { } + boost::shared_ptr<CreateImpromptuMUCUIEvent> createImpromptuMUCEvent = boost::dynamic_pointer_cast<CreateImpromptuMUCUIEvent>(event); + if (createImpromptuMUCEvent) { + assert(!localMUCServiceJID_.toString().empty()); + // create new muc + JID roomJID = createImpromptuMUCEvent->getRoomJID().toString().empty() ? JID(idGenerator_.generateID(), localMUCServiceJID_) : createImpromptuMUCEvent->getRoomJID(); + + // join muc + MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true); + mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, createImpromptuMUCEvent->getJIDs(), createImpromptuMUCEvent->getReason(), boost::optional<JID>())); + mucControllers_[roomJID]->activateChatWindow(); + } boost::shared_ptr<EditMUCBookmarkUIEvent> editMUCBookmarkEvent = boost::dynamic_pointer_cast<EditMUCBookmarkUIEvent>(event); @@ -390,5 +581,5 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { } else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) { - handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getPassword(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically(), joinEvent->getCreateAsReservedRoomIfNew()); + handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getPassword(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically(), joinEvent->getCreateAsReservedRoomIfNew(), joinEvent->isImpromptu()); mucControllers_[joinEvent->getJID()]->activateChatWindow(); } @@ -412,4 +603,20 @@ void ChatsManager::markAllRecentsOffline() { } +void ChatsManager::handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason) { + JID reuseChatInvite = chatController->getToJID(); + chatControllers_.erase(chatController->getToJID()); + delete chatController; + + // join new impromptu muc + assert(!localMUCServiceJID_.toString().empty()); + + // create new muc + JID roomJID = JID(idGenerator_.generateID(), localMUCServiceJID_); + + // join muc + MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true, chatWindow); + mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, jidsToInvite, reason, boost::optional<JID>(reuseChatInvite))); +} + /** * If a resource goes offline, release bound chatdialog to that resource. @@ -489,4 +696,9 @@ void ChatsManager::setOnline(bool enabled) { } else { setupBookmarks(); + localMUCServiceFinderWalker_ = boost::make_shared<DiscoServiceWalker>(jid_.getDomain(), iqRouter_); + localMUCServiceFinderWalker_->onServiceFound.connect(boost::bind(&ChatsManager::handleLocalServiceFound, this, _1, _2)); + localMUCServiceFinderWalker_->onWalkAborted.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this)); + localMUCServiceFinderWalker_->onWalkComplete.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this)); + localMUCServiceFinderWalker_->beginWalk(); } @@ -513,10 +725,14 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact) ChatController* ChatsManager::createNewChatController(const JID& contact) { assert(chatControllers_.find(contact) == chatControllers_.end()); - ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_); + boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), false); /* a message parser that knows this is a chat (not a room/MUC) */ + ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, autoAcceptMUCInviteDecider_); chatControllers_[contact] = controller; controller->setAvailableServerFeatures(serverDiscoInfo_); controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false)); + controller->onWindowClosed.connect(boost::bind(&ChatsManager::handleChatClosed, this, contact)); controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); + controller->onConvertToMUC.connect(boost::bind(&ChatsManager::handleTransformChatToMUC, this, controller, _1, _2, _3)); updatePresenceReceivingStateOnChatController(contact); + controller->setCanStartImpromptuChats(!localMUCServiceJID_.toString().empty()); return controller; } @@ -545,5 +761,4 @@ ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool return pair.second; } - } } @@ -560,8 +775,9 @@ void ChatsManager::rebindControllerJID(const JID& from, const JID& to) { } -void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& password, const boost::optional<std::string>& nickMaybe, bool addAutoJoin, bool createAsReservedIfNew) { +MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& password, const boost::optional<std::string>& nickMaybe, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow) { + MUC::ref muc; if (!stanzaChannel_->isAvailable()) { /* This is potentially not the optimal solution, but it will avoid consistency issues.*/ - return; + return muc; } if (addAutoJoin) { @@ -582,13 +798,31 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional } else { std::string nick = (nickMaybe && !(*nickMaybe).empty()) ? nickMaybe.get() : nickResolver_->jidToNick(jid_); - MUC::ref muc = mucManager->createMUC(mucJID); + muc = mucManager->createMUC(mucJID); if (createAsReservedIfNew) { muc->setCreateAsReservedIfNew(); } - MUCController* controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_); + if (isImpromptu) { + muc->setCreateAsReservedIfNew(); + } + + MUCController* controller = NULL; + SingleChatWindowFactoryAdapter* chatWindowFactoryAdapter = NULL; + if (reuseChatwindow) { + chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow); + } + boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), true); /* a message parser that knows this is a room/MUC (not a chat) */ + controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_); + if (chatWindowFactoryAdapter) { + /* The adapters are only passed to chat windows, which are deleted in their + * controllers' dtor, which are deleted in ChatManager's dtor. The adapters + * are also deleted there.*/ + chatWindowFactoryAdapters_[controller] = chatWindowFactoryAdapter; + } + mucControllers_[mucJID] = controller; controller->setAvailableServerFeatures(serverDiscoInfo_); controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller)); controller->onUserJoined.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), "", true)); + controller->onUserNicknameChanged.connect(boost::bind(&ChatsManager::handleUserNicknameChanged, this, controller, _1, _2)); controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), _1, true)); controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); @@ -597,4 +831,5 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional mucControllers_[mucJID]->showChatWindow(); + return muc; } @@ -603,8 +838,31 @@ void ChatsManager::handleSearchMUCRequest() { } +void ChatsManager::handleUserNicknameChanged(MUCController* mucController, const std::string& oldNickname, const std::string& newNickname) { + JID oldMUCChatJID = mucController->getToJID().withResource(oldNickname); + JID newMUCChatJID = mucController->getToJID().withResource(newNickname); + + SWIFT_LOG(debug) << "nickname change in " << mucController->getToJID().toString() << " from " << oldNickname << " to " << newNickname << std::endl; + + // get current chat controller + ChatController *chatController = getChatControllerIfExists(oldMUCChatJID); + if (chatController) { + // adjust chat controller + chatController->setToJID(newMUCChatJID); + nickResolver_->onNickChanged(newMUCChatJID, oldNickname); + chatControllers_.erase(oldMUCChatJID); + chatControllers_[newMUCChatJID] = chatController; + + chatController->onActivity.disconnect(boost::bind(&ChatsManager::handleChatActivity, this, oldMUCChatJID, _1, false)); + chatController->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, newMUCChatJID, _1, false)); + /*for (std::list<ChatListWindow::Chat>::iterator i = recentChats_.begin(); i != recentChats_.end(); i++) { + if (i->jid == + }*/ + } +} + void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) { JID jid = message->getFrom(); boost::shared_ptr<MessageEvent> event(new MessageEvent(message)); - bool isInvite = message->getPayload<MUCInvitationPayload>(); + bool isInvite = !!message->getPayload<MUCInvitationPayload>(); bool isMediatedInvite = (message->getPayload<MUCUserPayload>() && message->getPayload<MUCUserPayload>()->getInvite()); if (isMediatedInvite) { @@ -628,7 +886,34 @@ void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) { } + // check for impromptu invite to potentially auto-accept + MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>(); + if (invite && autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) { + if (invite->getIsContinuation()) { + // check for existing chat controller for the from JID + ChatController* controller = getChatControllerIfExists(jid); + if (controller) { + ChatWindow* window = controller->detachChatWindow(); + chatControllers_.erase(jid); + delete controller; + handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true, window); + return; + } + } else { + handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true); + return; + } + } + //if not a mucroom + if (!event->isReadable() && !isInvite && !isMediatedInvite) { + /* Only route such messages if a window exists, don't open new windows for them.*/ + ChatController* controller = getChatControllerIfExists(jid); + if (controller) { + controller->handleIncomingMessage(event); + } + } else { getChatControllerOrCreate(jid)->handleIncomingMessage(event); } +} void ChatsManager::handleMUCSelectedAfterSearch(const JID& muc) { @@ -648,6 +933,37 @@ void ChatsManager::handleNewFileTransferController(FileTransferController* ftc) } +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) { + if (chat.isMUC && !chat.impromptuJIDs.empty()) { + typedef std::pair<std::string, JID> StringJIDPair; + std::vector<JID> inviteJIDs; + foreach(StringJIDPair pair, chat.impromptuJIDs) { + inviteJIDs.push_back(pair.second); + } + uiEventStream_->send(boost::make_shared<CreateImpromptuMUCUIEvent>(inviteJIDs, chat.jid, "")); + } + else if (chat.isMUC) { /* FIXME: This means that recents requiring passwords will just flat-out not work */ uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(chat.jid, boost::optional<std::string>(), chat.nick)); @@ -658,3 +974,56 @@ void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) { } +void ChatsManager::handleLocalServiceFound(const JID& service, boost::shared_ptr<DiscoInfo> info) { + foreach (DiscoInfo::Identity identity, info->getIdentities()) { + if ((identity.getCategory() == "directory" + && identity.getType() == "chatroom") + || (identity.getCategory() == "conference" + && identity.getType() == "text")) { + localMUCServiceJID_ = service; + localMUCServiceFinderWalker_->endWalk(); + SWIFT_LOG(debug) << "Use following MUC service for impromptu chats: " << localMUCServiceJID_ << std::endl; + break; + } + } +} + +void ChatsManager::handleLocalServiceWalkFinished() { + onImpromptuMUCServiceDiscovered(!localMUCServiceJID_.toString().empty()); +} + +std::vector<ChatListWindow::Chat> ChatsManager::getRecentChats() const { + return std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end()); +} + +std::vector<Contact::ref> Swift::ChatsManager::getContacts(bool withMUCNicks) { + std::vector<Contact::ref> result; + foreach (ChatListWindow::Chat chat, recentChats_) { + if (!chat.isMUC) { + result.push_back(boost::make_shared<Contact>(chat.chatName.empty() ? chat.jid.toString() : chat.chatName, chat.jid, chat.statusType, chat.avatarPath)); + } + } + if (withMUCNicks) { + /* collect MUC nicks */ + typedef std::map<JID, MUCController*>::value_type Item; + foreach (const Item& item, mucControllers_) { + JID mucJID = item.second->getToJID(); + std::map<std::string, JID> participants = item.second->getParticipantJIDs(); + typedef std::map<std::string, JID>::value_type ParticipantType; + foreach (const ParticipantType& participant, participants) { + const JID nickJID = JID(mucJID.getNode(), mucJID.getDomain(), participant.first); + Presence::ref presence = presenceOracle_->getLastPresence(nickJID); + const boost::filesystem::path avatar = avatarManager_->getAvatarPath(nickJID); + result.push_back(boost::make_shared<Contact>(participant.first, JID(), presence->getShow(), avatar)); + } + } + } + return result; +} + +ChatsManager::SingleChatWindowFactoryAdapter::SingleChatWindowFactoryAdapter(ChatWindow* chatWindow) : chatWindow_(chatWindow) {} +ChatsManager::SingleChatWindowFactoryAdapter::~SingleChatWindowFactoryAdapter() {} +ChatWindow* ChatsManager::SingleChatWindowFactoryAdapter::createChatWindow(const JID &, UIEventStream*) { + return chatWindow_; +} + } diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index a8c69c4..82daf67 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -8,8 +8,9 @@ #include <map> +#include <string> #include <boost/shared_ptr.hpp> -#include <string> +#include <Swiften/Base/IDGenerator.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/Elements/Message.h> @@ -17,7 +18,14 @@ #include <Swiften/JID/JID.h> #include <Swiften/MUC/MUCRegistry.h> +#include <Swiften/MUC/MUCBookmark.h> +#include <Swiften/MUC/MUC.h> + + +#include <Swift/Controllers/ContactProvider.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIInterfaces/ChatListWindow.h> -#include <Swiften/MUC/MUCBookmark.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> + namespace Swift { @@ -27,5 +35,4 @@ namespace Swift { class MUCController; class MUCManager; - class ChatWindowFactory; class JoinMUCWindow; class JoinMUCWindowFactory; @@ -48,8 +55,17 @@ namespace Swift { class XMPPRoster; class SettingsProvider; + class WhiteboardManager; + class HistoryController; + class HighlightManager; + class ClientBlockListManager; + class ChatMessageParser; + class DiscoServiceWalker; + class AutoAcceptMUCInviteDecider; + class UserSearchController; + class VCardManager; - class ChatsManager { + class ChatsManager : public ContactProvider { 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, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons, UserSearchController* inviteUserSearchController, VCardManager* vcardManager); virtual ~ChatsManager(); void setAvatarManager(AvatarManager* avatarManager); @@ -57,9 +73,25 @@ namespace Swift { void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info); void handleIncomingMessage(boost::shared_ptr<Message> message); + std::vector<ChatListWindow::Chat> getRecentChats() const; + virtual std::vector<Contact::ref> getContacts(bool withMUCNicks); + + boost::signal<void (bool supportsImpromptu)> onImpromptuMUCServiceDiscovered; private: - ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity); + class SingleChatWindowFactoryAdapter : public ChatWindowFactory { + public: + SingleChatWindowFactoryAdapter(ChatWindow* chatWindow); + virtual ~SingleChatWindowFactoryAdapter(); + virtual ChatWindow* createChatWindow(const JID &, UIEventStream*); + + private: + ChatWindow* chatWindow_; + }; + + private: + ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity, bool privateMessage); void handleChatRequest(const std::string& contact); - void handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& password, const boost::optional<std::string>& nick, bool addAutoJoin, bool createAsReservedIfNew); + void finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID = boost::optional<JID>()); + MUC::ref handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& password, const boost::optional<std::string>& nick, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow = 0); void handleSearchMUCRequest(); void handleMUCSelectedAfterSearch(const JID&); @@ -70,7 +102,13 @@ namespace Swift { void handleMUCBookmarkRemoved(const MUCBookmark& bookmark); void handleUserLeftMUC(MUCController* mucController); + void handleUserNicknameChanged(MUCController* mucController, const std::string& oldNickname, const std::string& newNickname); void handleBookmarksReady(); void handleChatActivity(const JID& jid, const std::string& activity, bool isMUC); + void handleChatClosed(const JID& jid); void handleNewFileTransferController(FileTransferController*); + void handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf); + void handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state); + boost::optional<ChatListWindow::Chat> removeExistingChat(const ChatListWindow::Chat& chat); + void cleanupPrivateMessageRecents(); void appendRecent(const ChatListWindow::Chat& chat); void prependRecent(const ChatListWindow::Chat& chat); @@ -90,6 +128,12 @@ namespace Swift { void handleSettingChanged(const std::string& settingPath); void markAllRecentsOffline(); + void handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason); + + void handleLocalServiceFound(const JID& service, boost::shared_ptr<DiscoInfo> info); + void handleLocalServiceWalkFinished(); void updatePresenceReceivingStateOnChatController(const JID&); + ChatListWindow::Chat updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const; + ChatController* getChatControllerOrFindAnother(const JID &contact); @@ -101,4 +145,5 @@ namespace Swift { std::map<JID, MUCController*> mucControllers_; std::map<JID, ChatController*> chatControllers_; + std::map<ChatControllerBase*, SingleChatWindowFactoryAdapter*> chatWindowFactoryAdapters_; EventController* eventController_; JID jid_; @@ -130,4 +175,15 @@ namespace Swift { bool userWantsReceipts_; SettingsProvider* settings_; + HistoryController* historyController_; + WhiteboardManager* whiteboardManager_; + HighlightManager* highlightManager_; + std::map<std::string, std::string> emoticons_; + ClientBlockListManager* clientBlockListManager_; + JID localMUCServiceJID_; + boost::shared_ptr<DiscoServiceWalker> localMUCServiceFinderWalker_; + AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; + UserSearchController* inviteUserSearchController_; + IDGenerator idGenerator_; + VCardManager* vcardManager_; }; } diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index e4209f4..fe90c60 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -11,28 +11,38 @@ #include <boost/algorithm/string.hpp> -#include <Swift/Controllers/Intl.h> +#include <Swiften/Avatars/AvatarManager.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/foreach.h> #include <Swiften/Base/format.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Client/StanzaChannel.h> +#include <Swiften/Disco/EntityCapsProvider.h> +#include <Swiften/Elements/Delay.h> +#include <Swiften/MUC/MUC.h> #include <Swiften/Network/Timer.h> #include <Swiften/Network/TimerFactory.h> -#include <Swiften/Base/foreach.h> +#include <Swiften/Roster/XMPPRoster.h> + #include <SwifTools/TabComplete.h> -#include <Swiften/Base/foreach.h> -#include <Swift/Controllers/XMPPEvents/EventController.h> -#include <Swift/Controllers/UIInterfaces/ChatWindow.h> -#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> -#include <Swift/Controllers/UIEvents/UIEventStream.h> -#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> -#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> -#include <Swift/Controllers/Roster/GroupRosterItem.h> + +#include <Swift/Controllers/Chat/ChatMessageParser.h> +#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/Intl.h> #include <Swift/Controllers/Roster/ContactRosterItem.h> -#include <Swiften/Avatars/AvatarManager.h> -#include <Swiften/Elements/Delay.h> -#include <Swiften/MUC/MUC.h> -#include <Swiften/Client/StanzaChannel.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h> +#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> +#include <Swift/Controllers/Roster/ItemOperations/SetMUC.h> #include <Swift/Controllers/Roster/Roster.h> -#include <Swift/Controllers/Roster/SetAvatar.h> -#include <Swift/Controllers/Roster/SetPresence.h> -#include <Swiften/Disco/EntityCapsProvider.h> - +#include <Swift/Controllers/Roster/RosterVCardProvider.h> +#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> #define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000 @@ -57,6 +67,14 @@ MUCController::MUCController ( TimerFactory* timerFactory, EventController* eventController, - EntityCapsProvider* entityCapsProvider) : - ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password) { + EntityCapsProvider* entityCapsProvider, + XMPPRoster* roster, + HistoryController* historyController, + MUCRegistry* mucRegistry, + HighlightManager* highlightManager, + boost::shared_ptr<ChatMessageParser> chatMessageParser, + bool isImpromptu, + AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, + VCardManager* vcardManager) : + ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false) { parting_ = true; joined_ = false; @@ -65,18 +83,20 @@ MUCController::MUCController ( doneGettingHistory_ = false; events_ = uiEventStream; + xmppRoster_ = roster; roster_ = new Roster(false, true); + rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithResource); completer_ = new TabComplete(); chatWindow_->setRosterModel(roster_); chatWindow_->setTabComplete(completer_); - chatWindow_->setName(muc->getJID().getNode()); chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this)); chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1)); chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2)); chatWindow_->onChangeSubjectRequest.connect(boost::bind(&MUCController::handleChangeSubjectRequest, this, _1)); + chatWindow_->onBookmarkRequest.connect(boost::bind(&MUCController::handleBookmarkRequest, this)); chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1)); chatWindow_->onConfigurationFormCancelled.connect(boost::bind(&MUCController::handleConfigurationCancelled, this)); chatWindow_->onDestroyRequest.connect(boost::bind(&MUCController::handleDestroyRoomRequest, this)); - chatWindow_->onInvitePersonToThisMUCRequest.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this, _1, _2)); + chatWindow_->onInviteToChat.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this, _1)); chatWindow_->onGetAffiliationsRequest.connect(boost::bind(&MUCController::handleGetAffiliationsRequest, this)); chatWindow_->onChangeAffiliationsRequest.connect(boost::bind(&MUCController::handleChangeAffiliationsRequest, this, _1)); @@ -84,12 +104,13 @@ MUCController::MUCController ( muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1)); muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1)); + muc_->onOccupantNicknameChanged.connect(boost::bind(&MUCController::handleOccupantNicknameChanged, this, _1, _2)); muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1)); muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3)); - muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3)); - muc_->onOccupantAffiliationChanged.connect(boost::bind(&MUCController::handleOccupantAffiliationChanged, this, _1, _2, _3)); - muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1)); - muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1)); muc_->onRoleChangeFailed.connect(boost::bind(&MUCController::handleOccupantRoleChangeFailed, this, _1, _2, _3)); muc_->onAffiliationListReceived.connect(boost::bind(&MUCController::handleAffiliationListReceived, this, _1, _2)); + muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1)); + muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1)); + highlighter_->setMode(isImpromptu_ ? Highlighter::ChatMode : Highlighter::MUCMode); + highlighter_->setNick(nick_); if (timerFactory) { loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS)); @@ -97,5 +118,13 @@ MUCController::MUCController ( loginCheckTimer_->start(); } - chatWindow_->convertToMUC(); + if (isImpromptu) { + muc_->onUnlocked.connect(boost::bind(&MUCController::handleRoomUnlocked, this)); + chatWindow_->convertToMUC(ChatWindow::ImpromptuMUC); + } else { + muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3)); + muc_->onOccupantAffiliationChanged.connect(boost::bind(&MUCController::handleOccupantAffiliationChanged, this, _1, _2, _3)); + chatWindow_->convertToMUC(ChatWindow::StandardMUC); + chatWindow_->setName(muc->getJID().getNode()); + } setOnline(true); if (avatarManager_ != NULL) { @@ -103,8 +132,11 @@ MUCController::MUCController ( } handleBareJIDCapsChanged(muc->getJID()); + eventStream_->onUIEvent.connect(boost::bind(&MUCController::handleUIEvent, this, _1)); } MUCController::~MUCController() { + eventStream_->onUIEvent.disconnect(boost::bind(&MUCController::handleUIEvent, this, _1)); chatWindow_->setRosterModel(NULL); + delete rosterVCardProvider_; delete roster_; if (loginCheckTimer_) { @@ -125,5 +157,5 @@ void MUCController::handleWindowOccupantSelectionChanged(ContactRosterItem* item MUCOccupant::Affiliation affiliation = muc_->getOccupant(getNick()).getAffiliation(); MUCOccupant::Role role = muc_->getOccupant(getNick()).getRole(); - if (role == MUCOccupant::Moderator) + if (role == MUCOccupant::Moderator && !isImpromptu_) { if (affiliation == MUCOccupant::Admin || affiliation == MUCOccupant::Owner) { @@ -140,4 +172,5 @@ void MUCController::handleWindowOccupantSelectionChanged(ContactRosterItem* item actions.push_back(ChatWindow::AddContact); } + actions.push_back(ChatWindow::ShowProfile); } chatWindow_->setAvailableOccupantActions(actions); @@ -158,4 +191,5 @@ void MUCController::handleActionRequestedOnOccupant(ChatWindow::OccupantAction a case ChatWindow::MakeVisitor: muc_->changeOccupantRole(mucJID, MUCOccupant::Visitor);break; case ChatWindow::AddContact: if (occupant.getRealJID()) events_->send(boost::make_shared<RequestAddUserDialogUIEvent>(realJID, occupant.getNick()));break; + case ChatWindow::ShowProfile: events_->send(boost::make_shared<ShowProfileForRosterItemUIEvent>(mucJID));break; } } @@ -189,7 +223,13 @@ void MUCController::rejoin() { } //FIXME: check for received activity +#ifdef SWIFT_EXPERIMENTAL_HISTORY + if (lastActivity_ == boost::posix_time::not_a_date_time && historyController_) { + lastActivity_ = historyController_->getLastTimeStampFromMUC(selfJID_, toJID_); + } +#endif if (lastActivity_ == boost::posix_time::not_a_date_time) { muc_->joinAs(nick_); - } else { + } + else { muc_->joinWithContextSince(nick_, lastActivity_); } @@ -205,7 +245,33 @@ const std::string& MUCController::getNick() { } +const boost::optional<std::string> MUCController::getPassword() const { + return password_; +} + +bool MUCController::isImpromptu() const { + return isImpromptu_; +} + +std::map<std::string, JID> MUCController::getParticipantJIDs() const { + std::map<std::string, JID> participants; + typedef std::pair<std::string, MUCOccupant> MUCOccupantPair; + std::map<std::string, MUCOccupant> occupants = muc_->getOccupants(); + foreach(const MUCOccupantPair& occupant, occupants) { + if (occupant.first != nick_) { + participants[occupant.first] = occupant.second.getRealJID().is_initialized() ? occupant.second.getRealJID().get().toBare() : JID(); + } + } + return participants; +} + +void MUCController::sendInvites(const std::vector<JID>& jids, const std::string& reason) const { + foreach (const JID& jid, jids) { + muc_->invitePerson(jid, reason, isImpromptu_); + } +} + void MUCController::handleJoinTimeoutTick() { receivedActivity(); - chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection); } @@ -216,4 +282,7 @@ void MUCController::receivedActivity() { } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wswitch-enum" + void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) { receivedActivity(); @@ -255,24 +324,46 @@ void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) { } errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't join room: %1%.")) % errorMessage); - chatWindow_->addErrorMessage(errorMessage); + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); parting_ = true; - if (!rejoinNick.empty()) { - nick_ = rejoinNick; + if (!rejoinNick.empty() && renameCounter_ < 10) { + renameCounter_++; + setNick(rejoinNick); rejoin(); } } +#pragma clang diagnostic pop + void MUCController::handleJoinComplete(const std::string& nick) { receivedActivity(); + renameCounter_ = 0; joined_ = true; - std::string joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick); - nick_ = nick; - chatWindow_->addSystemMessage(joinMessage); + std::string joinMessage; + if (isImpromptu_) { + joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have joined the chat as %1%.")) % nick); + } else { + joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick); + } + setNick(nick); + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(joinMessage), ChatWindow::UpdateTimestamp); + +#ifdef SWIFT_EXPERIMENTAL_HISTORY + addRecentLogs(); +#endif + clearPresenceQueue(); shouldJoinOnReconnect_ = true; setEnabled(true); + if (isImpromptu_) { + setAvailableRoomActions(MUCOccupant::NoAffiliation, MUCOccupant::Participant); + } else { MUCOccupant occupant = muc_->getOccupant(nick); setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole()); + } onUserJoined(); + + if (isImpromptu_) { + setImpromptuWindowTitle(); + } } @@ -281,6 +372,5 @@ void MUCController::handleAvatarChanged(const JID& jid) { return; } - std::string path = avatarManager_->getAvatarPath(jid).string(); - roster_->applyOnItems(SetAvatar(jid, path, JID::WithResource)); + roster_->applyOnItems(SetAvatar(jid, avatarManager_->getAvatarPath(jid), JID::WithResource)); } @@ -305,15 +395,21 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) { NickJoinPart event(occupant.getNick(), Join); appendToJoinParts(joinParts_, event); - std::string groupName(roleToGroupName(occupant.getRole())); - roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid).string()); - roster_->getGroup(groupName)->setManualSort(roleToSortName(occupant.getRole())); + MUCOccupant::Role role = MUCOccupant::Participant; + MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation; + if (!isImpromptu_) { + role = occupant.getRole(); + affiliation = occupant.getAffiliation(); + } + std::string groupName(roleToGroupName(role)); + roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid)); + roster_->applyOnItems(SetMUC(jid, role, affiliation)); + roster_->getGroup(groupName)->setManualSort(roleToSortName(role)); if (joined_) { std::string joinString; - MUCOccupant::Role role = occupant.getRole(); if (role != MUCOccupant::NoRole && role != MUCOccupant::Participant) { - joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the room as a %2%.")) % occupant.getNick() % roleToFriendlyName(role)); + joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %3% as a %2%.")) % occupant.getNick() % roleToFriendlyName(role) % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room"))); } else { - joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the room.")) % occupant.getNick()); + joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %2%.")) % occupant.getNick() % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room"))); } if (shouldUpdateJoinParts()) { @@ -321,5 +417,9 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) { } else { addPresenceMessage(joinString); + } + if (isImpromptu_) { + setImpromptuWindowTitle(); + onActivity(""); } } @@ -331,5 +431,5 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) { void MUCController::addPresenceMessage(const std::string& message) { lastWasPresence_ = true; - chatWindow_->addPresenceMessage(message); + chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(message), ChatWindow::DefaultDirection); } @@ -369,4 +469,5 @@ std::string MUCController::roleToFriendlyName(MUCOccupant::Role role) { case MUCOccupant::NoRole: return ""; } + assert(false); return ""; } @@ -379,9 +480,10 @@ std::string MUCController::roleToSortName(MUCOccupant::Role role) { case MUCOccupant::NoRole: return "4"; } + assert(false); return "5"; } JID MUCController::nickToJID(const std::string& nick) { - return JID(toJID_.getNode(), toJID_.getDomain(), nick); + return muc_->getJID().withResource(nick); } @@ -398,6 +500,5 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes clearPresenceQueue(); boost::shared_ptr<Message> message = messageEvent->getStanza(); - if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>() && messageEvent->isReadable() -) { + if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>() && messageEvent->isReadable()) { chatWindow_->flash(); } @@ -405,4 +506,7 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes messageEvent->setTargetsMe(false); } + if (messageEvent->isReadable() && isImpromptu_) { + chatWindow_->flash(); /* behave like a regular char*/ + } if (joined_) { std::string nick = message->getFrom().getResource(); @@ -416,5 +520,5 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes if (message->hasSubject() && message->getBody().empty()) { - chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject()));; + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject())), ChatWindow::DefaultDirection);; chatWindow_->setSubject(message->getSubject()); doneGettingHistory_ = true; @@ -426,12 +530,18 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes if (!doneGettingHistory_) { + checkDuplicates(message); messageEvent->conclude(); } } -void MUCController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { +void MUCController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) { boost::shared_ptr<Message> message = messageEvent->getStanza(); - if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>()) { + if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && !message->getPayload<Delay>()) { + if (messageTargetsMe(message) || isImpromptu_) { eventController_->handleIncomingEvent(messageEvent); + if (!messageEvent->getConcluded()) { + highlighter_->handleHighlightAction(highlight); + } + } } } @@ -447,7 +557,8 @@ void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUC } std::string group(roleToGroupName(occupant.getRole())); - roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid).string()); + roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid)); roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole())); - chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))); + roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation())); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection); if (nick == nick_) { setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole()); @@ -460,4 +571,7 @@ void MUCController::handleOccupantAffiliationChanged(const std::string& nick, co setAvailableRoomActions(affiliation, muc_->getOccupant(nick_).getRole()); } + JID jid(nickToJID(nick)); + MUCOccupant occupant = muc_->getOccupant(nick); + roster_->applyOnItems(SetMUC(jid, occupant.getRole(), affiliation)); } @@ -469,5 +583,4 @@ std::string MUCController::roleToGroupName(MUCOccupant::Role role) { case MUCOccupant::Visitor: result = QT_TRANSLATE_NOOP("", "Visitors"); break; case MUCOccupant::NoRole: result = QT_TRANSLATE_NOOP("", "Occupants"); break; - default: assert(false); } return result; @@ -482,9 +595,14 @@ void MUCController::setOnline(bool online) { } else { if (shouldJoinOnReconnect_) { - chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())); + renameCounter_ = 0; + if (isImpromptu_) { + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "Trying to join chat")), ChatWindow::DefaultDirection); + } else { + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection); + } if (loginCheckTimer_) { loginCheckTimer_->start(); } - nick_ = desiredNick_; + setNick(desiredNick_); rejoin(); } @@ -523,6 +641,20 @@ void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::Leaving case MUC::LeavePart: break; } + if (isImpromptu_) { + partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the chat%2%")) % occupant.getNick() % partType); + } else { partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the room%2%")) % occupant.getNick() % partType); } + } + else if (isImpromptu_) { + switch (type) { + case MUC::LeaveKick: + case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been removed from this chat"); break; + case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been removed from this chat"); break; + case MUC::LeaveDestroy: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "This chat has ended"); break; + case MUC::Disconnect: + case MUC::LeavePart: partMessage = QT_TRANSLATE_NOOP("", "You have left the chat"); + } + } else { switch (type) { @@ -555,4 +687,46 @@ void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::Leaving clearPresenceQueue(); } + + if (isImpromptu_) { + setImpromptuWindowTitle(); + } +} + +void MUCController::handleOccupantNicknameChanged(const std::string& oldNickname, const std::string& newNickname) { + addPresenceMessage(generateNicknameChangeString(oldNickname, newNickname)); + JID oldJID = muc_->getJID().withResource(oldNickname); + JID newJID = muc_->getJID().withResource(newNickname); + + // adjust occupants + currentOccupants_.erase(oldNickname); + currentOccupants_.insert(newNickname); + + // adjust completer + completer_->removeWord(oldNickname); + completer_->addWord(newNickname); + + // update contact + roster_->removeContact(oldJID); + MUCOccupant occupant = muc_->getOccupant(newNickname); + + JID realJID; + if (occupant.getRealJID()) { + realJID = occupant.getRealJID().get(); + } + MUCOccupant::Role role = MUCOccupant::Participant; + MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation; + if (!isImpromptu_) { + role = occupant.getRole(); + affiliation = occupant.getAffiliation(); + } + std::string groupName(roleToGroupName(role)); + roster_->addContact(newJID, realJID, newNickname, groupName, avatarManager_->getAvatarPath(newJID)); + roster_->applyOnItems(SetMUC(newJID, role, affiliation)); + if (avatarManager_ != NULL) { + handleAvatarChanged(newJID); + } + + clearPresenceQueue(); + onUserNicknameChanged(oldNickname, newNickname); } @@ -580,5 +754,5 @@ boost::optional<boost::posix_time::ptime> MUCController::getMessageTimestamp(boo void MUCController::updateJoinParts() { - chatWindow_->replaceLastMessage(generateJoinPartString(joinParts_)); + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(generateJoinPartString(joinParts_, isImpromptu())), ChatWindow::UpdateTimestamp); } @@ -593,5 +767,6 @@ void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, cons case Join: type = (type == Part) ? PartThenJoin : Join; break; case Part: type = (type == Join) ? JoinThenPart : Part; break; - default: /*Nothing to see here */;break; + case PartThenJoin: break; + case JoinThenPart: break; } (*it).type = type; @@ -620,5 +795,5 @@ std::string MUCController::concatenateListOfNames(const std::vector<NickJoinPart } -std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart>& joinParts) { +std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu) { std::vector<NickJoinPart> sorted[4]; std::string eventStrings[4]; @@ -635,32 +810,32 @@ std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart case Join: if (sorted[i].size() > 1) { - eventString = QT_TRANSLATE_NOOP("", "%1% have entered the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined the chat") : QT_TRANSLATE_NOOP("", "%1% have entered the room")); } else { - eventString = QT_TRANSLATE_NOOP("", "%1% has entered the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined the chat") : QT_TRANSLATE_NOOP("", "%1% has entered the room")); } break; case Part: if (sorted[i].size() > 1) { - eventString = QT_TRANSLATE_NOOP("", "%1% have left the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% have left the room")); } else { - eventString = QT_TRANSLATE_NOOP("", "%1% has left the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% has left the room")); } break; case JoinThenPart: if (sorted[i].size() > 1) { - eventString = QT_TRANSLATE_NOOP("", "%1% have entered then left the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% have entered then left the room")); } else { - eventString = QT_TRANSLATE_NOOP("", "%1% has entered then left the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% has entered then left the room")); } break; case PartThenJoin: if (sorted[i].size() > 1) { - eventString = QT_TRANSLATE_NOOP("", "%1% have left then returned to the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% have left then returned to the room")); } else { - eventString = QT_TRANSLATE_NOOP("", "%1% has left then returned to the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% has left then returned to the room")); } break; @@ -683,8 +858,20 @@ std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart } +std::string MUCController::generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname) { + return str(boost::format(QT_TRANSLATE_NOOP("", "%1% is now known as %2%.")) % oldNickname % newNickname); +} + void MUCController::handleChangeSubjectRequest(const std::string& subject) { muc_->changeSubject(subject); } +void MUCController::handleBookmarkRequest() { + const JID jid = muc_->getJID(); + MUCBookmark bookmark(jid, jid.toBare().toString()); + bookmark.setPassword(password_); + bookmark.setNick(nick_); + chatWindow_->showBookmarkWindow(bookmark); +} + void MUCController::handleConfigureRequest(Form::ref form) { if (form) { @@ -699,5 +886,5 @@ void MUCController::handleConfigurationFailed(ErrorPayload::ref error) { std::string errorMessage = getErrorMessage(error); errorMessage = str(format(QT_TRANSLATE_NOOP("", "Room configuration failed: %1%.")) % errorMessage); - chatWindow_->addErrorMessage(errorMessage); + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); } @@ -705,10 +892,22 @@ void MUCController::handleOccupantRoleChangeFailed(ErrorPayload::ref error, cons std::string errorMessage = getErrorMessage(error); errorMessage = str(format(QT_TRANSLATE_NOOP("", "Occupant role change failed: %1%.")) % errorMessage); - chatWindow_->addErrorMessage(errorMessage); + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); +} + +void MUCController::configureAsImpromptuRoom(Form::ref form) { + muc_->configureRoom(buildImpromptuRoomConfiguration(form)); + isImpromptuAlreadyConfigured_ = true; + onImpromptuConfigCompleted(); } void MUCController::handleConfigurationFormReceived(Form::ref form) { + if (isImpromptu_) { + if (!isImpromptuAlreadyConfigured_) { + configureAsImpromptuRoom(form); + } + } else { chatWindow_->showRoomConfigurationForm(form); } +} void MUCController::handleConfigurationCancelled() { @@ -720,6 +919,17 @@ void MUCController::handleDestroyRoomRequest() { } -void MUCController::handleInvitePersonToThisMUCRequest(const JID& jid, const std::string& reason) { - muc_->invitePerson(jid, reason); +void MUCController::handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite) { + RequestInviteToMUCUIEvent::ImpromptuMode mode = isImpromptu_ ? RequestInviteToMUCUIEvent::Impromptu : RequestInviteToMUCUIEvent::NotImpromptu; + boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(muc_->getJID(), jidsToInvite, mode)); + eventStream_->send(event); +} + +void MUCController::handleUIEvent(boost::shared_ptr<UIEvent> event) { + boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event); + if (inviteEvent && inviteEvent->getRoom() == muc_->getJID()) { + foreach (const JID& jid, inviteEvent->getInvites()) { + muc_->invitePerson(jid, inviteEvent->getReason(), isImpromptu_); + } + } } @@ -751,3 +961,124 @@ void MUCController::handleAffiliationListReceived(MUCOccupant::Affiliation affil } +void MUCController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) { + // log only incoming messages + if (isIncoming && historyController_) { + historyController_->addMessage(message, fromJID, toJID, HistoryMessage::Groupchat, timeStamp); + } +} + +void MUCController::addRecentLogs() { + if (!historyController_) { + return; + } + + joinContext_ = historyController_->getMUCContext(selfJID_, toJID_, lastActivity_); + + foreach (const HistoryMessage& message, joinContext_) { + bool senderIsSelf = nick_ == message.getFromJID().getResource(); + + // the chatWindow uses utc timestamps + addMessage(message.getMessage(), senderDisplayNameFromMessage(message.getFromJID()), senderIsSelf, boost::shared_ptr<SecurityLabel>(new SecurityLabel()), avatarManager_->getAvatarPath(message.getFromJID()), message.getTime() - boost::posix_time::hours(message.getOffset()), HighlightAction()); + } +} + +void MUCController::checkDuplicates(boost::shared_ptr<Message> newMessage) { + std::string body = newMessage->getBody(); + JID jid = newMessage->getFrom(); + boost::optional<boost::posix_time::ptime> time = newMessage->getTimestamp(); + + reverse_foreach (const HistoryMessage& message, joinContext_) { + boost::posix_time::ptime messageTime = message.getTime() - boost::posix_time::hours(message.getOffset()); + if (time && time < messageTime) { + break; + } + if (time && time != messageTime) { + continue; + } + if (message.getFromJID() != jid) { + continue; + } + if (message.getMessage() != body) { + continue; + } + + // Mark the message as unreadable + newMessage->setBody(""); + } +} + +void MUCController::setNick(const std::string& nick) { + nick_ = nick; + highlighter_->setNick(nick_); +} + +Form::ref MUCController::buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm) { + Form::ref result = boost::make_shared<Form>(Form::SubmitType); + std::string impromptuConfigs[] = { "muc#roomconfig_enablelogging", "muc#roomconfig_persistentroom", "muc#roomconfig_publicroom", "muc#roomconfig_whois"}; + std::set<std::string> impromptuConfigsMissing(impromptuConfigs, impromptuConfigs + 4); + foreach (boost::shared_ptr<FormField> field, roomConfigurationForm->getFields()) { + boost::shared_ptr<FormField> resultField; + if (field->getName() == "muc#roomconfig_enablelogging") { + resultField = boost::make_shared<FormField>(FormField::BooleanType, "0"); + } + if (field->getName() == "muc#roomconfig_persistentroom") { + resultField = boost::make_shared<FormField>(FormField::BooleanType, "0"); + } + if (field->getName() == "muc#roomconfig_publicroom") { + resultField = boost::make_shared<FormField>(FormField::BooleanType, "0"); + } + if (field->getName() == "muc#roomconfig_whois") { + resultField = boost::make_shared<FormField>(FormField::ListSingleType, "anyone"); + } + + if (field->getName() == "FORM_TYPE") { + resultField = boost::make_shared<FormField>(FormField::HiddenType, "http://jabber.org/protocol/muc#roomconfig"); + } + + if (resultField) { + impromptuConfigsMissing.erase(field->getName()); + resultField->setName(field->getName()); + result->addField(resultField); + } + } + + foreach (const std::string& config, impromptuConfigsMissing) { + if (config == "muc#roomconfig_publicroom") { + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support hiding your chat from other users.")), ChatWindow::DefaultDirection); + } else if (config == "muc#roomconfig_whois") { + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support sharing people's real identity in this chat.")), ChatWindow::DefaultDirection); + } + } + + return result; +} + +void MUCController::setImpromptuWindowTitle() { + std::string title; + typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair; + std::map<std::string, MUCOccupant> occupants = muc_->getOccupants(); + if (occupants.size() <= 1) { + title = QT_TRANSLATE_NOOP("", "Empty Chat"); + } else { + foreach (StringMUCOccupantPair pair, occupants) { + if (pair.first != nick_) { + title += (title.empty() ? "" : ", ") + pair.first; + } + } + } + chatWindow_->setName(title); +} + +void MUCController::handleRoomUnlocked() { + // Handle buggy MUC implementations where the joined room already exists and is unlocked. + // Configure the room again in this case. + if (!isImpromptuAlreadyConfigured_) { + if (isImpromptu_ && (muc_->getOccupant(nick_).getAffiliation() == MUCOccupant::Owner)) { + muc_->requestConfigurationForm(); + } else if (isImpromptu_) { + onImpromptuConfigCompleted(); + } + } +} + } diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index 9550ca9..2d732a1 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,12 +7,13 @@ #pragma once +#include <set> +#include <string> +#include <map> + #include <boost/shared_ptr.hpp> -#include <Swiften/Base/boost_bsignals.h> #include <boost/signals/connection.hpp> -#include <set> -#include <string> +#include <Swiften/Base/boost_bsignals.h> #include <Swiften/Network/Timer.h> -#include <Swift/Controllers/Chat/ChatControllerBase.h> #include <Swiften/Elements/Message.h> #include <Swiften/Elements/DiscoInfo.h> @@ -20,4 +21,6 @@ #include <Swiften/MUC/MUC.h> #include <Swiften/Elements/MUCOccupant.h> + +#include <Swift/Controllers/Chat/ChatControllerBase.h> #include <Swift/Controllers/Roster/RosterItem.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> @@ -32,9 +35,14 @@ namespace Swift { class TimerFactory; class TabComplete; + class XMPPRoster; + class HighlightManager; + class UIEvent; + class VCardManager; + class RosterVCardProvider; enum JoinPart {Join, Part, JoinThenPart, PartThenJoin}; struct NickJoinPart { - NickJoinPart(const std::string& nick, JoinPart type) : nick(nick), type(type) {}; + NickJoinPart(const std::string& nick, JoinPart type) : nick(nick), type(type) {} std::string nick; JoinPart type; @@ -43,15 +51,22 @@ namespace Swift { class MUCController : public ChatControllerBase { public: - MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider); - ~MUCController(); + MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager); + virtual ~MUCController(); boost::signal<void ()> onUserLeft; boost::signal<void ()> onUserJoined; + boost::signal<void ()> onImpromptuConfigCompleted; + boost::signal<void (const std::string&, const std::string& )> onUserNicknameChanged; virtual void setOnline(bool online); void rejoin(); static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent); - static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts); + static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu); static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts); + static std::string generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname); bool isJoined(); const std::string& getNick(); + const boost::optional<std::string> getPassword() const; + bool isImpromptu() const; + std::map<std::string, JID> getParticipantJIDs() const; + void sendInvites(const std::vector<JID>& jids, const std::string& reason) const; protected: @@ -61,6 +76,7 @@ namespace Swift { boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message> message) const; void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>); - void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>); + void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>, const HighlightAction&); void cancelReplaces(); + void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming); private: @@ -73,4 +89,5 @@ namespace Swift { void handleAvatarChanged(const JID& jid); void handleOccupantJoined(const MUCOccupant& occupant); + void handleOccupantNicknameChanged(const std::string& oldNickname, const std::string& newNickname); void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const std::string& reason); void handleOccupantPresenceChange(boost::shared_ptr<Presence> presence); @@ -81,4 +98,5 @@ namespace Swift { void handleJoinTimeoutTick(); void handleChangeSubjectRequest(const std::string&); + void handleBookmarkRequest(); std::string roleToGroupName(MUCOccupant::Role role); std::string roleToSortName(MUCOccupant::Role role); @@ -96,5 +114,5 @@ namespace Swift { void handleConfigurationFormReceived(Form::ref); void handleDestroyRoomRequest(); - void handleInvitePersonToThisMUCRequest(const JID& jid, const std::string& reason); + void handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite); void handleConfigurationCancelled(); void handleOccupantRoleChangeFailed(ErrorPayload::ref, const JID&, MUCOccupant::Role); @@ -102,4 +120,14 @@ namespace Swift { void handleAffiliationListReceived(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids); void handleChangeAffiliationsRequest(const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& changes); + void handleInviteToMUCWindowDismissed(); + void handleInviteToMUCWindowCompleted(); + void handleUIEvent(boost::shared_ptr<UIEvent> event); + void addRecentLogs(); + void checkDuplicates(boost::shared_ptr<Message> newMessage); + void setNick(const std::string& nick); + void setImpromptuWindowTitle(); + void handleRoomUnlocked(); + void configureAsImpromptuRoom(Form::ref form); + Form::ref buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm); private: @@ -121,4 +149,10 @@ namespace Swift { boost::posix_time::ptime lastActivity_; boost::optional<std::string> password_; + XMPPRoster* xmppRoster_; + std::vector<HistoryMessage> joinContext_; + size_t renameCounter_; + bool isImpromptu_; + bool isImpromptuAlreadyConfigured_; + RosterVCardProvider* rosterVCardProvider_; }; } diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp new file mode 100644 index 0000000..2a07654 --- /dev/null +++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2013-2014 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <hippomocks.h> + +#include <Swift/Controllers/Chat/ChatMessageParser.h> + +using namespace Swift; + +class ChatMessageParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(ChatMessageParserTest); + CPPUNIT_TEST(testFullBody); + CPPUNIT_TEST(testOneEmoticon); + CPPUNIT_TEST(testBareEmoticon); + CPPUNIT_TEST(testHiddenEmoticon); + CPPUNIT_TEST(testEndlineEmoticon); + CPPUNIT_TEST(testBoundedEmoticons); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() { + smile1_ = ":)"; + smile1Path_ = "/blah/smile1.png"; + smile2_ = ":("; + smile2Path_ = "/blah/smile2.jpg"; + emoticons_[smile1_] = smile1Path_; + emoticons_[smile2_] = smile2Path_; + } + + void tearDown() { + emoticons_.clear(); + } + + void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + boost::shared_ptr<ChatWindow::ChatTextMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]); + CPPUNIT_ASSERT_EQUAL(text, part->text); + } + + void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) { + boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]); + CPPUNIT_ASSERT(!!part); + CPPUNIT_ASSERT_EQUAL(text, part->alternativeText); + CPPUNIT_ASSERT_EQUAL(path, part->imagePath); + } + + void assertHighlight(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(result.getParts()[index]); + CPPUNIT_ASSERT_EQUAL(text, part->text); + } + + void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + boost::shared_ptr<ChatWindow::ChatURIMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]); + CPPUNIT_ASSERT_EQUAL(text, part->target); + } + + static HighlightRule ruleFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) + { + HighlightRule rule; + std::vector<std::string> keywords; + keywords.push_back(keyword); + rule.setKeywords(keywords); + rule.setMatchCase(matchCase); + rule.setMatchWholeWords(matchWholeWord); + rule.setMatchChat(true); + return rule; + } + + static const HighlightRulesListPtr ruleListFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) + { + boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); + list->addRule(ruleFromKeyword(keyword, matchCase, matchWholeWord)); + return list; + } + + static const HighlightRulesListPtr ruleListFromKeywords(const HighlightRule &rule1, const HighlightRule &rule2) + { + boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); + list->addRule(rule1); + list->addRule(rule2); + return list; + } + + static HighlightRulesListPtr ruleListWithNickHighlight() + { + HighlightRule rule; + rule.setMatchChat(true); + rule.setNickIsKeyword(true); + rule.setMatchCase(true); + rule.setMatchWholeWords(true); + boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); + list->addRule(rule); + return list; + } + + void testFullBody() { + const std::string no_special_message = "a message with no special content"; + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(no_special_message); + assertText(result, 0, no_special_message); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody(":) shiny :( trigger :) http://wonderland.lit/blah http://denmark.lit boom boom"); + assertEmoticon(result, 0, smile1_, smile1Path_); + assertText(result, 1, " shiny "); + assertEmoticon(result, 2, smile2_, smile2Path_); + assertText(result, 3, " "); + assertHighlight(result, 4, "trigger"); + assertText(result, 5, " "); + assertEmoticon(result, 6, smile1_, smile1Path_); + assertText(result, 7, " "); + assertURL(result, 8, "http://wonderland.lit/blah"); + assertText(result, 9, " "); + assertURL(result, 10, "http://denmark.lit"); + assertText(result, 11, " boom boom"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody("testtriggermessage"); + assertText(result, 0, "test"); + assertHighlight(result, 1, "trigger"); + assertText(result, 2, "message"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, true)); + result = testling.parseMessageBody("testtriggermessage"); + assertText(result, 0, "testtriggermessage"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", true, false)); + result = testling.parseMessageBody("TrIgGeR"); + assertText(result, 0, "TrIgGeR"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody("TrIgGeR"); + assertHighlight(result, 0, "TrIgGeR"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody("partialTrIgGeRmatch"); + assertText(result, 0, "partial"); + assertHighlight(result, 1, "TrIgGeR"); + assertText(result, 2, "match"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zero one two three"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "one"); + assertText(result, 2, " two "); + assertHighlight(result, 3, "three"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "oNe"); + assertText(result, 2, " two "); + assertHighlight(result, 3, "tHrEe"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", true, false))); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "oNe"); + assertText(result, 2, " two tHrEe"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", true, false))); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero oNe two tHrEe"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zeroonetwothree"); + assertText(result, 0, "zero"); + assertHighlight(result, 1, "one"); + assertText(result, 2, "two"); + assertHighlight(result, 3, "three"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zeroOnEtwoThReE"); + assertText(result, 0, "zeroOnEtwo"); + assertHighlight(result, 1, "ThReE"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zeroonetwothree"); + assertText(result, 0, "zeroonetwo"); + assertHighlight(result, 1, "three"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, true))); + result = testling.parseMessageBody("zeroonetwothree"); + assertText(result, 0, "zeroonetwothree"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Alice", "Alice"); + assertHighlight(result, 0, "Alice"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("TextAliceText", "Alice"); + assertText(result, 0, "TextAliceText"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Text Alice Text", "Alice"); + assertText(result, 0, "Text "); + assertHighlight(result, 1, "Alice"); + assertText(result, 2, " Text"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Alice Text", "Alice"); + assertHighlight(result, 0, "Alice"); + assertText(result, 1, " Text"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Text Alice", "Alice"); + assertText(result, 0, "Text "); + assertHighlight(result, 1, "Alice"); + } + + void testOneEmoticon() { + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(" :) "); + assertText(result, 0, " "); + assertEmoticon(result, 1, smile1_, smile1Path_); + assertText(result, 2, " "); + } + + + void testBareEmoticon() { + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(":)"); + assertEmoticon(result, 0, smile1_, smile1Path_); + } + + void testHiddenEmoticon() { + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a"); + assertText(result, 0, "b:)a"); + } + + void testEndlineEmoticon() { + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody("Lazy:)"); + assertText(result, 0, "Lazy"); + assertEmoticon(result, 1, smile1_, smile1Path_); + } + + void testBoundedEmoticons() { + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(":)Lazy:("); + assertEmoticon(result, 0, smile1_, smile1Path_); + assertText(result, 1, "Lazy"); + assertEmoticon(result, 2, smile2_, smile2Path_); + } + + void testEmoticonParenthesis() { + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))"); + assertText(result, 0, "(Like this "); + assertEmoticon(result, 1, smile1_, smile1Path_); + assertText(result, 2, ")"); + } + +private: + std::map<std::string, std::string> emoticons_; + std::string smile1_; + std::string smile1Path_; + std::string smile2_; + std::string smile2Path_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageParserTest); diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index bbfb22f..4c604ac 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -7,47 +7,56 @@ #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> -#include "3rdParty/hippomocks.h" + +#include <hippomocks.h> #include <boost/bind.hpp> -#include "Swift/Controllers/Chat/ChatsManager.h" - -#include "Swift/Controllers/Chat/UnitTest/MockChatListWindow.h" -#include "Swift/Controllers/UIInterfaces/ChatWindow.h" -#include "Swift/Controllers/Settings/DummySettingsProvider.h" -#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/ChatListWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h" -#include "Swiften/Client/Client.h" -#include "Swiften/Disco/EntityCapsManager.h" -#include "Swiften/Disco/CapsProvider.h" -#include "Swiften/MUC/MUCManager.h" -#include "Swift/Controllers/Chat/ChatController.h" -#include "Swift/Controllers/XMPPEvents/EventController.h" -#include "Swift/Controllers/Chat/MUCController.h" -#include "Swiften/Presence/StanzaChannelPresenceSender.h" -#include "Swiften/Avatars/NullAvatarManager.h" -#include "Swiften/Avatars/AvatarMemoryStorage.h" -#include "Swiften/VCards/VCardManager.h" -#include "Swiften/VCards/VCardMemoryStorage.h" -#include "Swiften/Client/NickResolver.h" -#include "Swiften/Presence/DirectedPresenceSender.h" -#include "Swiften/Roster/XMPPRosterImpl.h" -#include "Swift/Controllers/UnitTest/MockChatWindow.h" -#include "Swiften/Client/DummyStanzaChannel.h" -#include "Swiften/Queries/DummyIQChannel.h" -#include "Swiften/Presence/PresenceOracle.h" -#include "Swiften/Jingle/JingleSessionManager.h" -#include "Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h" -#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" -#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include <Swift/Controllers/ProfileSettingsProvider.h> -#include "Swift/Controllers/FileTransfer/FileTransferOverview.h" -#include "Swiften/Elements/DeliveryReceiptRequest.h" -#include "Swiften/Elements/DeliveryReceipt.h" +#include <Swiften/Avatars/AvatarMemoryStorage.h> +#include <Swiften/Avatars/NullAvatarManager.h> #include <Swiften/Base/Algorithm.h> +#include <Swiften/Client/Client.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/Client/DummyStanzaChannel.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/Crypto/CryptoProvider.h> +#include <Swiften/Crypto/PlatformCryptoProvider.h> +#include <Swiften/Disco/CapsProvider.h> +#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Elements/DeliveryReceipt.h> +#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> +#include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/MUC/MUCManager.h> +#include <Swiften/Presence/DirectedPresenceSender.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Presence/StanzaChannelPresenceSender.h> +#include <Swiften/Queries/DummyIQChannel.h> +#include <Swiften/Roster/XMPPRosterImpl.h> +#include <Swiften/VCards/VCardManager.h> +#include <Swiften/VCards/VCardManager.h> +#include <Swiften/VCards/VCardMemoryStorage.h> +#include <Swiften/Whiteboard/WhiteboardSessionManager.h> + +#include <Swift/Controllers/Chat/ChatsManager.h> +#include <Swift/Controllers/Chat/ChatController.h> +#include <Swift/Controllers/Chat/MUCController.h> +#include <Swift/Controllers/Chat/UnitTest/MockChatListWindow.h> +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> +#include <Swift/Controllers/ProfileSettingsProvider.h> #include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/Settings/DummySettingsProvider.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h> +#include <Swift/Controllers/UnitTest/MockChatWindow.h> +#include <Swift/Controllers/WhiteboardManager.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> + using namespace Swift; @@ -101,19 +110,33 @@ public: ftManager_ = new DummyFileTransferManager(); ftOverview_ = new FileTransferOverview(ftManager_); + avatarManager_ = new NullAvatarManager(); + wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsManager_); + wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_); + highlightManager_ = new HighlightManager(settings_); + crypto_ = PlatformCryptoProvider::create(); + vcardStorage_ = new VCardMemoryStorage(crypto_); + vcardManager_ = new VCardManager(jid_, iqRouter_, vcardStorage_); 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_); + clientBlockListManager_ = new ClientBlockListManager(iqRouter_); + manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, NULL, vcardManager_); - avatarManager_ = new NullAvatarManager(); manager_->setAvatarManager(avatarManager_); - }; + } void tearDown() { + delete highlightManager_; //delete chatListWindowFactory delete profileSettings_; delete avatarManager_; delete manager_; + delete clientBlockListManager_; + delete vcardManager_; + delete vcardStorage_; + delete crypto_; delete ftOverview_; delete ftManager_; + delete wbSessionManager_; + delete wbManager_; delete directedPresenceSender_; delete presenceSender_; @@ -434,4 +457,5 @@ private: message->setFrom(from); message->setID(id); + message->setBody("This will cause the window to open"); message->addPayload(boost::make_shared<DeliveryReceiptRequest>()); return message; @@ -460,4 +484,5 @@ private: UIEventStream* uiEventStream_; ChatListWindowFactory* chatListWindowFactory_; + WhiteboardWindowFactory* whiteboardWindowFactory_; MUCSearchWindowFactory* mucSearchWindowFactory_; MUCRegistry* mucRegistry_; @@ -471,4 +496,12 @@ private: FileTransferOverview* ftOverview_; FileTransferManager* ftManager_; + WhiteboardSessionManager* wbSessionManager_; + WhiteboardManager* wbManager_; + HighlightManager* highlightManager_; + ClientBlockListManager* clientBlockListManager_; + VCardManager* vcardManager_; + CryptoProvider* crypto_; + VCardStorage* vcardStorage_; + std::map<std::string, std::string> emoticons_; }; diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp index d4fbcfd..bb22e43 100644 --- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -8,6 +8,7 @@ #include <cppunit/extensions/TestFactoryRegistry.h> #include <boost/algorithm/string.hpp> -#include "3rdParty/hippomocks.h" +#include <hippomocks.h> +#include "Swiften/Base/foreach.h" #include "Swift/Controllers/XMPPEvents/EventController.h" #include "Swiften/Presence/DirectedPresenceSender.h" @@ -21,4 +22,5 @@ #include "Swift/Controllers/UIEvents/UIEventStream.h" #include "Swift/Controllers/UnitTest/MockChatWindow.h" +#include "Swiften/MUC/UnitTest/MockMUC.h" #include "Swiften/Client/DummyStanzaChannel.h" #include "Swiften/Queries/DummyIQChannel.h" @@ -27,4 +29,14 @@ #include "Swiften/Elements/MUCUserPayload.h" #include "Swiften/Disco/DummyEntityCapsProvider.h" +#include <Swiften/VCards/VCardMemoryStorage.h> +#include <Swiften/Crypto/PlatformCryptoProvider.h> +#include <Swiften/VCards/VCardManager.h> +#include <Swift/Controllers/Settings/DummySettingsProvider.h> +#include <Swift/Controllers/Chat/ChatMessageParser.h> +#include <Swift/Controllers/Chat/UserSearchController.h> +#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swiften/Crypto/CryptoProvider.h> using namespace Swift; @@ -40,8 +52,11 @@ class MUCControllerTest : public CppUnit::TestFixture { CPPUNIT_TEST(testMessageWithEmptyLabelItem); CPPUNIT_TEST(testMessageWithLabelItem); + CPPUNIT_TEST(testCorrectMessageWithLabelItem); + CPPUNIT_TEST(testRoleAffiliationStates); CPPUNIT_TEST_SUITE_END(); public: void setUp() { + crypto_ = boost::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create()); self_ = JID("girl@wonderland.lit/rabbithole"); nick_ = "aLiCe"; @@ -53,4 +68,5 @@ public: eventController_ = new EventController(); chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); + userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>(); presenceOracle_ = new PresenceOracle(stanzaChannel_); presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); @@ -62,12 +78,21 @@ public: mucRegistry_ = new MUCRegistry(); entityCapsProvider_ = new DummyEntityCapsProvider(); - muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_); + settings_ = new DummySettingsProvider(); + highlightManager_ = new HighlightManager(settings_); + muc_ = boost::make_shared<MockMUC>(mucJID_); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); - controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_); - }; + chatMessageParser_ = boost::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getRules(), true); + vcardStorage_ = new VCardMemoryStorage(crypto_.get()); + vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_); + controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_, false, NULL, vcardManager_); + } void tearDown() { - delete entityCapsProvider_; delete controller_; + delete vcardManager_; + delete vcardStorage_; + delete highlightManager_; + delete settings_; + delete entityCapsProvider_; delete eventController_; delete presenceOracle_; @@ -185,5 +210,5 @@ public: void testMessageWithLabelItem() { - SecurityLabel::ref label = boost::make_shared<SecurityLabel>(); + boost::shared_ptr<SecurityLabel> label = boost::make_shared<SecurityLabel>(); label->setLabel("a"); SecurityLabelsCatalog::Item labelItem; @@ -211,4 +236,42 @@ public: } + void testCorrectMessageWithLabelItem() { + boost::shared_ptr<SecurityLabel> label = boost::make_shared<SecurityLabel>(); + label->setLabel("a"); + SecurityLabelsCatalog::Item labelItem; + labelItem.setSelector("Bob"); + labelItem.setLabel(label); + boost::shared_ptr<SecurityLabel> label2 = boost::make_shared<SecurityLabel>(); + label->setLabel("b"); + SecurityLabelsCatalog::Item labelItem2; + labelItem2.setSelector("Charlie"); + labelItem2.setLabel(label2); + window_->label_ = labelItem; + boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>(); + features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); + controller_->setAvailableServerFeatures(features); + IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; + SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>(); + labelPayload->addItem(labelItem); + IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); + iqChannel_->onIQReceived(result); + std::string messageBody("agamemnon"); + window_->onSendMessageRequest(messageBody, false); + boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza); + CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom()); + CPPUNIT_ASSERT(window_->labelsEnabled_); + CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + CPPUNIT_ASSERT(message); + CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody()); + CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); + window_->label_ = labelItem2; + window_->onSendMessageRequest(messageBody, true); + rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + message = boost::dynamic_pointer_cast<Message>(rawStanza); + CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody()); + CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); + } + void checkEqual(const std::vector<NickJoinPart>& expected, const std::vector<NickJoinPart>& actual) { CPPUNIT_ASSERT_EQUAL(expected.size(), actual.size()); @@ -258,11 +321,11 @@ public: std::vector<NickJoinPart> list; list.push_back(NickJoinPart("Kev", Join)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list, false)); list.push_back(NickJoinPart("Remko", Part)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); list.push_back(NickJoinPart("Bert", Join)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); list.push_back(NickJoinPart("Ernie", Join)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); } @@ -270,11 +333,74 @@ public: std::vector<NickJoinPart> list; list.push_back(NickJoinPart("Kev", JoinThenPart)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list, false)); list.push_back(NickJoinPart("Remko", Part)); - CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list, false)); list.push_back(NickJoinPart("Bert", PartThenJoin)); - CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); list.push_back(NickJoinPart("Ernie", JoinThenPart)); - CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); + } + + JID jidFromOccupant(const MUCOccupant& occupant) { + return JID(mucJID_.toString()+"/"+occupant.getNick()); + } + + void testRoleAffiliationStates() { + + typedef std::map<std::string, MUCOccupant> occupant_map; + occupant_map occupants; + occupants.insert(occupant_map::value_type("Kev", MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Remko", MUCOccupant("Remko", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Bert", MUCOccupant("Bert", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Ernie", MUCOccupant("Ernie", MUCOccupant::Participant, MUCOccupant::Owner))); + + /* populate the MUC with fake users */ + typedef const std::pair<std::string,MUCOccupant> occupantIterator; + foreach(occupantIterator &occupant, occupants) { + muc_->insertOccupant(occupant.second); + } + + std::vector<MUCOccupant> alterations; + alterations.push_back(MUCOccupant("Kev", MUCOccupant::Visitor, MUCOccupant::Admin)); + alterations.push_back(MUCOccupant("Remko", MUCOccupant::Moderator, MUCOccupant::Member)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::Outcast)); + alterations.push_back(MUCOccupant("Ernie", MUCOccupant::NoRole, MUCOccupant::Member)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Moderator, MUCOccupant::Owner)); + alterations.push_back(MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Outcast)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::NoAffiliation)); + alterations.push_back(MUCOccupant("Remko", MUCOccupant::NoRole, MUCOccupant::NoAffiliation)); + alterations.push_back(MUCOccupant("Ernie", MUCOccupant::Visitor, MUCOccupant::Outcast)); + + foreach(const MUCOccupant& alteration, alterations) { + /* perform an alteration to a user's role and affiliation */ + occupant_map::iterator occupant = occupants.find(alteration.getNick()); + CPPUNIT_ASSERT(occupant != occupants.end()); + const JID jid = jidFromOccupant(occupant->second); + /* change the affiliation, leave the role in place */ + muc_->changeAffiliation(jid, alteration.getAffiliation()); + occupant->second = MUCOccupant(occupant->first, occupant->second.getRole(), alteration.getAffiliation()); + testRoleAffiliationStatesVerify(occupants); + /* change the role, leave the affiliation in place */ + muc_->changeOccupantRole(jid, alteration.getRole()); + occupant->second = MUCOccupant(occupant->first, alteration.getRole(), occupant->second.getAffiliation()); + testRoleAffiliationStatesVerify(occupants); + } + } + + void testRoleAffiliationStatesVerify(const std::map<std::string, MUCOccupant> &occupants) { + /* verify that the roster is in sync */ + GroupRosterItem* group = window_->getRosterModel()->getRoot(); + foreach(RosterItem* rosterItem, group->getChildren()) { + GroupRosterItem* child = dynamic_cast<GroupRosterItem*>(rosterItem); + CPPUNIT_ASSERT(child); + foreach(RosterItem* childItem, child->getChildren()) { + ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(childItem); + CPPUNIT_ASSERT(item); + std::map<std::string, MUCOccupant>::const_iterator occupant = occupants.find(item->getJID().getResource()); + CPPUNIT_ASSERT(occupant != occupants.end()); + CPPUNIT_ASSERT(item->getMUCRole() == occupant->second.getRole()); + CPPUNIT_ASSERT(item->getMUCAffiliation() == occupant->second.getAffiliation()); + } + } } @@ -282,5 +408,5 @@ private: JID self_; JID mucJID_; - MUC::ref muc_; + MockMUC::ref muc_; std::string nick_; DummyStanzaChannel* stanzaChannel_; @@ -289,4 +415,5 @@ private: EventController* eventController_; ChatWindowFactory* chatWindowFactory_; + UserSearchWindowFactory* userSearchWindowFactory_; MUCController* controller_; // NickResolver* nickResolver_; @@ -300,4 +427,10 @@ private: MUCRegistry* mucRegistry_; DummyEntityCapsProvider* entityCapsProvider_; + DummySettingsProvider* settings_; + HighlightManager* highlightManager_; + boost::shared_ptr<ChatMessageParser> chatMessageParser_; + boost::shared_ptr<CryptoProvider> crypto_; + VCardManager* vcardManager_; + VCardMemoryStorage* vcardStorage_; }; diff --git a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h index 6ac8d4a..287c4b9 100644 --- a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h +++ b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2011 Kevin Smith + * Copyright (c) 2011-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -13,12 +13,15 @@ namespace Swift { class MockChatListWindow : public ChatListWindow { public: - MockChatListWindow() {}; - virtual ~MockChatListWindow() {}; + MockChatListWindow() {} + 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*/) {} void clearBookmarks() {} + void setOnline(bool /*isOnline*/) {} }; diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp index 839f4fa..f1849c9 100644 --- a/Swift/Controllers/Chat/UserSearchController.cpp +++ b/Swift/Controllers/Chat/UserSearchController.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -16,16 +16,22 @@ #include <Swiften/Disco/DiscoServiceWalker.h> #include <Swiften/VCards/VCardManager.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Avatars/AvatarManager.h> #include <Swift/Controllers/ContactEditController.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h> #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h> #include <Swift/Controllers/UIInterfaces/UserSearchWindow.h> #include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h> #include <Swift/Controllers/Roster/RosterController.h> +#include <Swift/Controllers/ContactSuggester.h> namespace Swift { -UserSearchController::UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* factory, IQRouter* iqRouter, RosterController* rosterController) : type_(type), jid_(jid), uiEventStream_(uiEventStream), vcardManager_(vcardManager), factory_(factory), iqRouter_(iqRouter), rosterController_(rosterController) { +UserSearchController::UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* factory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle) : type_(type), jid_(jid), uiEventStream_(uiEventStream), vcardManager_(vcardManager), factory_(factory), iqRouter_(iqRouter), rosterController_(rosterController), contactSuggester_(contactSuggester), avatarManager_(avatarManager), presenceOracle_(presenceOracle) { uiEventStream_->onUIEvent.connect(boost::bind(&UserSearchController::handleUIEvent, this, _1)); vcardManager_->onVCardChanged.connect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2)); + avatarManager_->onAvatarChanged.connect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1)); + presenceOracle_->onPresenceChange.connect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1)); window_ = NULL; discoWalker_ = NULL; @@ -39,38 +45,61 @@ UserSearchController::~UserSearchController() { window_->onFormRequested.disconnect(boost::bind(&UserSearchController::handleFormRequested, this, _1)); window_->onSearchRequested.disconnect(boost::bind(&UserSearchController::handleSearch, this, _1, _2)); + window_->onJIDUpdateRequested.disconnect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1)); + window_->onJIDAddRequested.disconnect(boost::bind(&UserSearchController::handleJIDAddRequested, this, _1)); delete window_; } + presenceOracle_->onPresenceChange.disconnect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1)); + avatarManager_->onAvatarChanged.disconnect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1)); vcardManager_->onVCardChanged.disconnect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2)); uiEventStream_->onUIEvent.disconnect(boost::bind(&UserSearchController::handleUIEvent, this, _1)); } +UserSearchWindow* UserSearchController::getUserSearchWindow() { + initializeUserWindow(); + assert(window_); + return window_; +} + +void UserSearchController::setCanInitiateImpromptuMUC(bool supportsImpromptu) { + if (!window_) { + initializeUserWindow(); + } + window_->setCanStartImpromptuChats(supportsImpromptu); +} + void UserSearchController::handleUIEvent(boost::shared_ptr<UIEvent> event) { bool handle = false; - boost::shared_ptr<RequestAddUserDialogUIEvent> request = boost::shared_ptr<RequestAddUserDialogUIEvent>(); - if (type_ == AddContact) { - if ((request = boost::dynamic_pointer_cast<RequestAddUserDialogUIEvent>(event))) { + boost::shared_ptr<RequestAddUserDialogUIEvent> addUserRequest = boost::shared_ptr<RequestAddUserDialogUIEvent>(); + RequestInviteToMUCUIEvent::ref inviteToMUCRequest = RequestInviteToMUCUIEvent::ref(); + switch (type_) { + case AddContact: + if ((addUserRequest = boost::dynamic_pointer_cast<RequestAddUserDialogUIEvent>(event))) { handle = true; } - } else { + break; + case StartChat: if (boost::dynamic_pointer_cast<RequestChatWithUserDialogUIEvent>(event)) { handle = true; } + break; + case InviteToChat: + if ((inviteToMUCRequest = boost::dynamic_pointer_cast<RequestInviteToMUCUIEvent>(event))) { + handle = true; } - if (handle) { - if (!window_) { - window_ = factory_->createUserSearchWindow(type_ == AddContact ? UserSearchWindow::AddContact : UserSearchWindow::ChatToContact, uiEventStream_, rosterController_->getGroups()); - window_->onNameSuggestionRequested.connect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1)); - window_->onFormRequested.connect(boost::bind(&UserSearchController::handleFormRequested, this, _1)); - window_->onSearchRequested.connect(boost::bind(&UserSearchController::handleSearch, this, _1, _2)); - window_->setSelectedService(JID(jid_.getDomain())); - window_->clear(); + break; } + if (handle) { + initializeUserWindow(); window_->show(); - if (request) { - const std::string& name = request->getPredefinedName(); - const JID& jid = request->getPredefinedJID(); + if (addUserRequest) { + const std::string& name = addUserRequest->getPredefinedName(); + const JID& jid = addUserRequest->getPredefinedJID(); if (!name.empty() && jid.isValid()) { window_->prepopulateJIDAndName(jid, name); } + } else if (inviteToMUCRequest) { + window_->setCanSupplyDescription(!inviteToMUCRequest->isImpromptu()); + window_->setJIDs(inviteToMUCRequest->getInvites()); + window_->setRoomJID(inviteToMUCRequest->getRoom()); } return; @@ -99,5 +128,4 @@ void UserSearchController::endDiscoWalker() { } - void UserSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) { //bool isUserDirectory = false; @@ -167,4 +195,26 @@ void UserSearchController::handleNameSuggestionRequest(const JID &jid) { } +void UserSearchController::handleContactSuggestionsRequested(std::string text) { + const std::vector<JID> existingJIDs = window_->getJIDs(); + std::vector<Contact::ref> suggestions = contactSuggester_->getSuggestions(text, false); + /* do not suggest contacts that have already been added to the chat list */ + std::vector<Contact::ref>::iterator i = suggestions.begin(); + while (i != suggestions.end()) { + bool found = false; + foreach (const JID& jid, existingJIDs) { + if ((*i)->jid == jid) { + found = true; + break; + } + } + if (found) { + i = suggestions.erase(i); + } else { + i++; + } + } + window_->setContactSuggestions(suggestions); +} + void UserSearchController::handleVCardChanged(const JID& jid, VCard::ref vcard) { if (jid == suggestionsJID_) { @@ -172,4 +222,61 @@ void UserSearchController::handleVCardChanged(const JID& jid, VCard::ref vcard) suggestionsJID_ = JID(); } + handleJIDUpdateRequested(std::vector<JID>(1, jid)); +} + +void UserSearchController::handleAvatarChanged(const JID& jid) { + handleJIDUpdateRequested(std::vector<JID>(1, jid)); +} + +void UserSearchController::handlePresenceChanged(Presence::ref presence) { + handleJIDUpdateRequested(std::vector<JID>(1, presence->getFrom().toBare())); +} + +void UserSearchController::handleJIDUpdateRequested(const std::vector<JID>& jids) { + if (window_) { + std::vector<Contact::ref> updates; + foreach(const JID& jid, jids) { + updates.push_back(convertJIDtoContact(jid)); + } + window_->updateContacts(updates); + } +} + +void UserSearchController::handleJIDAddRequested(const std::vector<JID>& jids) { + std::vector<Contact::ref> contacts; + foreach(const JID& jid, jids) { + contacts.push_back(convertJIDtoContact(jid)); + } + window_->addContacts(contacts); +} + +Contact::ref UserSearchController::convertJIDtoContact(const JID& jid) { + Contact::ref contact = boost::make_shared<Contact>(); + contact->jid = jid; + + // name lookup + boost::optional<XMPPRosterItem> rosterItem = rosterController_->getItem(jid); + if (rosterItem && !rosterItem->getName().empty()) { + contact->name = rosterItem->getName(); + } else { + VCard::ref vcard = vcardManager_->getVCard(jid); + if (vcard && !vcard->getFullName().empty()) { + contact->name = vcard->getFullName(); + } else { + contact->name = jid.toString(); + } + } + + // presence lookup + Presence::ref presence = presenceOracle_->getHighestPriorityPresence(jid); + if (presence) { + contact->statusType = presence->getShow(); + } else { + contact->statusType = StatusShow::None; + } + + // avatar lookup + contact->avatarPath = avatarManager_->getAvatarPath(jid); + return contact; } @@ -179,3 +286,30 @@ void UserSearchController::handleDiscoWalkFinished() { } +void UserSearchController::initializeUserWindow() { + if (!window_) { + UserSearchWindow::Type windowType = UserSearchWindow::AddContact; + switch(type_) { + case AddContact: + windowType = UserSearchWindow::AddContact; + break; + case StartChat: + windowType = UserSearchWindow::ChatToContact; + break; + case InviteToChat: + windowType = UserSearchWindow::InviteToChat; + break; + } + + window_ = factory_->createUserSearchWindow(windowType, uiEventStream_, rosterController_->getGroups()); + window_->onNameSuggestionRequested.connect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1)); + window_->onFormRequested.connect(boost::bind(&UserSearchController::handleFormRequested, this, _1)); + window_->onSearchRequested.connect(boost::bind(&UserSearchController::handleSearch, this, _1, _2)); + window_->onContactSuggestionsRequested.connect(boost::bind(&UserSearchController::handleContactSuggestionsRequested, this, _1)); + window_->onJIDUpdateRequested.connect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1)); + window_->onJIDAddRequested.connect(boost::bind(&UserSearchController::handleJIDAddRequested, this, _1)); + window_->setSelectedService(JID(jid_.getDomain())); + window_->clear(); + } +} + } diff --git a/Swift/Controllers/Chat/UserSearchController.h b/Swift/Controllers/Chat/UserSearchController.h index ce0754c..d630580 100644 --- a/Swift/Controllers/Chat/UserSearchController.h +++ b/Swift/Controllers/Chat/UserSearchController.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -8,15 +8,18 @@ #include <boost/shared_ptr.hpp> + #include <map> #include <vector> -#include <Swiften/Base/boost_bsignals.h> - -#include <Swiften/Elements/SearchPayload.h> #include <string> -#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Contact.h> +#include <Swiften/Base/boost_bsignals.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/Elements/DiscoItems.h> #include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/SearchPayload.h> #include <Swiften/Elements/VCard.h> +#include <Swiften/JID/JID.h> namespace Swift { @@ -29,4 +32,7 @@ namespace Swift { class RosterController; class VCardManager; + class ContactSuggester; + class AvatarManager; + class PresenceOracle; class UserSearchResult { @@ -42,8 +48,11 @@ namespace Swift { class UserSearchController { public: - enum Type {AddContact, StartChat}; - UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* userSearchWindowFactory, IQRouter* iqRouter, RosterController* rosterController); + enum Type {AddContact, StartChat, InviteToChat}; + UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* userSearchWindowFactory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle); ~UserSearchController(); + UserSearchWindow* getUserSearchWindow(); + void setCanInitiateImpromptuMUC(bool supportsImpromptu); + private: void handleUIEvent(boost::shared_ptr<UIEvent> event); @@ -55,6 +64,13 @@ namespace Swift { void handleSearchResponse(boost::shared_ptr<SearchPayload> results, ErrorPayload::ref error); void handleNameSuggestionRequest(const JID& jid); + void handleContactSuggestionsRequested(std::string text); void handleVCardChanged(const JID& jid, VCard::ref vcard); + void handleAvatarChanged(const JID& jid); + void handlePresenceChanged(Presence::ref presence); + void handleJIDUpdateRequested(const std::vector<JID>& jids); + void handleJIDAddRequested(const std::vector<JID>& jids); + Contact::ref convertJIDtoContact(const JID& jid); void endDiscoWalker(); + void initializeUserWindow(); private: @@ -69,4 +85,7 @@ namespace Swift { UserSearchWindow* window_; DiscoServiceWalker* discoWalker_; + ContactSuggester* contactSuggester_; + AvatarManager* avatarManager_; + PresenceOracle* presenceOracle_; }; } diff --git a/Swift/Controllers/ChatMessageSummarizer.cpp b/Swift/Controllers/ChatMessageSummarizer.cpp index d95a177..014e032 100644 --- a/Swift/Controllers/ChatMessageSummarizer.cpp +++ b/Swift/Controllers/ChatMessageSummarizer.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2011 Kevin Smith + * Copyright (c) 2011-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -39,5 +39,5 @@ string ChatMessageSummarizer::getSummary(const string& current, const vector<Unr myString = str(format(result) % myString % others.size() % otherCount); } else if (!others.empty()) { - string result(QT_TRANSLATE_NOOP("", "%1%, %2% (%3%)")); + string result(QT_TRANSLATE_NOOP("", "%1%; %2% (%3%)")); myString = str(format(result) % myString % others[0].first % otherCount); } diff --git a/Swift/Controllers/ConnectionSettings.h b/Swift/Controllers/ConnectionSettings.h new file mode 100644 index 0000000..c02c5d4 --- /dev/null +++ b/Swift/Controllers/ConnectionSettings.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> + +struct ConnectionSettings { + enum Method { + Automatic, + Manual, + BOSH + }; + enum ProxyType { + None, + System, + SOCKS5, + HTTPConnect + }; + + Method method; + struct { + bool useManualServer; + std::string manualServerHostname; + int manualServerPort; + ProxyType proxyType; + bool useManualProxy; + std::string manualProxyHostname; + int manualProxyPort; + } manualSettings; + struct { + std::string boshURI; + bool useManualProxy; + std::string manualProxyHostname; + int manualProxyPort; + } boshSettings; +}; diff --git a/Swift/Controllers/Contact.cpp b/Swift/Controllers/Contact.cpp new file mode 100644 index 0000000..be2b83a --- /dev/null +++ b/Swift/Controllers/Contact.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/find.hpp> +#include <Swift/Controllers/Contact.h> + +namespace Swift { + +Contact::Contact() { +} + +Contact::Contact(const std::string& name, const JID& jid, StatusShow::Type statusType, const boost::filesystem::path& path) : name(name), jid(jid), statusType(statusType), avatarPath(path) { +} + +bool Contact::lexicographicalSortPredicate(const Contact::ref& a, const Contact::ref& b) { + if (a->jid.isValid() && b->jid.isValid()) { + return a->jid < b->jid; + } else { + return a->name < b->name; + } +} + +bool Contact::equalityPredicate(const Contact::ref& a, const Contact::ref& b) { + if (a->jid.isValid() && b->jid.isValid()) { + return a->jid == b->jid; + } else { + return a->name == b->name; + } +} + +bool Contact::sortPredicate(const Contact::ref& a, const Contact::ref& b, const std::string& search) { + /* perform case insensitive comparisons */ + std::string aLower = a->name; + boost::to_lower(aLower); + std::string bLower = b->name; + boost::to_lower(bLower); + std::string searchLower = search; + boost::to_lower(searchLower); + + /* name starts with the search term */ + if (aLower.find(searchLower) == 0 && bLower.find(searchLower) != 0) { + return true; + } else if (bLower.find(searchLower) == 0 && aLower.find(searchLower) != 0) { + return false; + } + + /* name contains search term */ + if (aLower.find(searchLower) != std::string::npos && bLower.find(searchLower) == std::string::npos) { + return true; + } else if (bLower.find(searchLower) != std::string::npos && aLower.find(searchLower) == std::string::npos) { + return false; + } + + /* Levenshtein should be done here */ + /* if edit distances are equal, fall through to the tests below */ + + /* lexicographical sort */ + if (a->statusType == b->statusType) { + return aLower.compare(bLower) < 0; + } + + /* online status */ + return a->statusType < b->statusType; +} + +} diff --git a/Swift/Controllers/Contact.h b/Swift/Controllers/Contact.h new file mode 100644 index 0000000..f83230f --- /dev/null +++ b/Swift/Controllers/Contact.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/enable_shared_from_this.hpp> +#include <boost/filesystem/path.hpp> + +#include <Swiften/Elements/StatusShow.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + +class Contact : public boost::enable_shared_from_this<Contact> { + public: + typedef boost::shared_ptr<Contact> ref; + + Contact(); + Contact(const std::string& name, const JID& jid, StatusShow::Type statusType, const boost::filesystem::path& path); + + static bool lexicographicalSortPredicate(const Contact::ref& a, const Contact::ref& b); + static bool equalityPredicate(const Contact::ref& a, const Contact::ref& b); + static bool sortPredicate(const Contact::ref& a, const Contact::ref& b, const std::string& search); + + public: + std::string name; + JID jid; + StatusShow::Type statusType; + boost::filesystem::path avatarPath; +}; + +} diff --git a/Swift/Controllers/ContactProvider.cpp b/Swift/Controllers/ContactProvider.cpp new file mode 100644 index 0000000..7dd1abf --- /dev/null +++ b/Swift/Controllers/ContactProvider.cpp @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/Controllers/ContactProvider.h> + +namespace Swift { + +ContactProvider::~ContactProvider() { + +} + +} diff --git a/Swift/Controllers/ContactProvider.h b/Swift/Controllers/ContactProvider.h new file mode 100644 index 0000000..acc2bdc --- /dev/null +++ b/Swift/Controllers/ContactProvider.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> + +#include <Swift/Controllers/Contact.h> + +namespace Swift { + +class ContactProvider { + public: + virtual ~ContactProvider(); + virtual std::vector<Contact::ref> getContacts(bool withMUCNicks) = 0; +}; + +} diff --git a/Swift/Controllers/ContactSuggester.cpp b/Swift/Controllers/ContactSuggester.cpp new file mode 100644 index 0000000..8627aeb --- /dev/null +++ b/Swift/Controllers/ContactSuggester.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/ContactSuggester.h> + +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/find.hpp> +#include <boost/bind.hpp> +#include <boost/lambda/lambda.hpp> +#include <boost/lambda/bind.hpp> + +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/ContactProvider.h> + +#include <algorithm> +#include <vector> +#include <set> + +namespace lambda = boost::lambda; + +namespace Swift { + +ContactSuggester::ContactSuggester() { +} + +ContactSuggester::~ContactSuggester() { +} + +void ContactSuggester::addContactProvider(ContactProvider* provider) { + contactProviders_.push_back(provider); +} + +bool ContactSuggester::matchContact(const std::string& search, const Contact::ref& c) { + if (fuzzyMatch(c->name, search)) { + return true; + } + else if (c->jid.isValid()) { + return fuzzyMatch(c->jid.toString(), search); + } + return false; +} + +std::vector<Contact::ref> ContactSuggester::getSuggestions(const std::string& search, bool withMUCNicks) const { + std::vector<Contact::ref> results; + + foreach(ContactProvider* provider, contactProviders_) { + append(results, provider->getContacts(withMUCNicks)); + } + + std::sort(results.begin(), results.end(), Contact::lexicographicalSortPredicate); + results.erase(std::unique(results.begin(), results.end(), Contact::equalityPredicate), results.end()); + results.erase(std::remove_if(results.begin(), results.end(), !lambda::bind(&matchContact, search, lambda::_1)), + results.end()); + std::sort(results.begin(), results.end(), boost::bind(&Contact::sortPredicate, _1, _2, search)); + + return results; +} + +bool ContactSuggester::fuzzyMatch(std::string text, std::string match) { + std::string lowerText = text; + boost::algorithm::to_lower(lowerText); + std::string lowerMatch = match; + boost::algorithm::to_lower(lowerMatch); + size_t lastMatch = 0; + for (size_t i = 0; i < lowerMatch.length(); ++i) { + size_t where = lowerText.find_first_of(lowerMatch[i], lastMatch); + if (where == std::string::npos) { + return false; + } + lastMatch = where + 1; + } + return true; +} + +} diff --git a/Swift/Controllers/ContactSuggester.h b/Swift/Controllers/ContactSuggester.h new file mode 100644 index 0000000..ae47766 --- /dev/null +++ b/Swift/Controllers/ContactSuggester.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> +#include <vector> + +#include <Swift/Controllers/Contact.h> + +class ContactSuggesterTest; + +namespace Swift { + class ContactProvider; + + class ContactSuggester { + public: + ContactSuggester(); + ~ContactSuggester(); + + void addContactProvider(ContactProvider* provider); + + std::vector<Contact::ref> getSuggestions(const std::string& search, bool withMUCNicks) const; + public: + static bool matchContact(const std::string& search, const Contact::ref& c); + /** + * Performs fuzzy matching on the string text. Matches when each character of match string is present in sequence in text string. + */ + static bool fuzzyMatch(std::string text, std::string match); + + private: + std::vector<ContactProvider*> contactProviders_; + }; +} diff --git a/Swift/Controllers/ContactsFromXMPPRoster.cpp b/Swift/Controllers/ContactsFromXMPPRoster.cpp new file mode 100644 index 0000000..abd62bd --- /dev/null +++ b/Swift/Controllers/ContactsFromXMPPRoster.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/ContactsFromXMPPRoster.h> + +#include <Swiften/Base/foreach.h> + +#include <Swiften/Avatars/AvatarManager.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Roster/XMPPRoster.h> +#include <Swiften/Roster/XMPPRosterItem.h> + +namespace Swift { + +ContactsFromXMPPRoster::ContactsFromXMPPRoster(XMPPRoster* roster, AvatarManager* avatarManager, PresenceOracle* presenceOracle) : roster_(roster), avatarManager_(avatarManager), presenceOracle_(presenceOracle) { +} + +ContactsFromXMPPRoster::~ContactsFromXMPPRoster() { +} + +std::vector<Contact::ref> ContactsFromXMPPRoster::getContacts(bool /*withMUCNicks*/) { + std::vector<Contact::ref> results; + std::vector<XMPPRosterItem> rosterItems = roster_->getItems(); + foreach(const XMPPRosterItem& rosterItem, rosterItems) { + Contact::ref contact = boost::make_shared<Contact>(rosterItem.getName().empty() ? rosterItem.getJID().toString() : rosterItem.getName(), rosterItem.getJID(), StatusShow::None,""); + contact->statusType = presenceOracle_->getHighestPriorityPresence(contact->jid) ? presenceOracle_->getHighestPriorityPresence(contact->jid)->getShow() : StatusShow::None; + contact->avatarPath = avatarManager_->getAvatarPath(contact->jid); + results.push_back(contact); + } + return results; +} + +} diff --git a/Swift/Controllers/ContactsFromXMPPRoster.h b/Swift/Controllers/ContactsFromXMPPRoster.h new file mode 100644 index 0000000..b76adc4 --- /dev/null +++ b/Swift/Controllers/ContactsFromXMPPRoster.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/ContactProvider.h> + +namespace Swift { + +class PresenceOracle; +class AvatarManager; +class XMPPRoster; + +class ContactsFromXMPPRoster : public ContactProvider { + public: + ContactsFromXMPPRoster(XMPPRoster* roster, AvatarManager* avatarManager, PresenceOracle* presenceOracle); + virtual ~ContactsFromXMPPRoster(); + + virtual std::vector<Contact::ref> getContacts(bool withMUCNicks); + private: + XMPPRoster* roster_; + AvatarManager* avatarManager_; + PresenceOracle* presenceOracle_; +}; + +} diff --git a/Swift/Controllers/DummySoundPlayer.h b/Swift/Controllers/DummySoundPlayer.h index 36dcb28..d0601c5 100644 --- a/Swift/Controllers/DummySoundPlayer.h +++ b/Swift/Controllers/DummySoundPlayer.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,10 +7,10 @@ #pragma once -#include "Swift/Controllers/SoundPlayer.h" +#include <Swift/Controllers/SoundPlayer.h> namespace Swift { class DummySoundPlayer : public SoundPlayer { public: - void playSound(SoundEffect sound) {}; + void playSound(SoundEffect sound, const std::string& soundResource) {} }; } diff --git a/Swift/Controllers/DummySystemTray.h b/Swift/Controllers/DummySystemTray.h index 41da4cd..451588e 100644 --- a/Swift/Controllers/DummySystemTray.h +++ b/Swift/Controllers/DummySystemTray.h @@ -12,6 +12,6 @@ namespace Swift { class DummySystemTray : public SystemTray { public: - void setUnreadMessages(bool some) {}; - void setStatusType(StatusShow::Type type) {}; + void setUnreadMessages(bool some) {} + void setStatusType(StatusShow::Type type) {} void setConnecting() {} }; diff --git a/Swift/Controllers/FileTransfer/FileTransferController.cpp b/Swift/Controllers/FileTransfer/FileTransferController.cpp index 3feaf49..0160a7a 100644 --- a/Swift/Controllers/FileTransfer/FileTransferController.cpp +++ b/Swift/Controllers/FileTransfer/FileTransferController.cpp @@ -11,4 +11,5 @@ #include <Swiften/Base/boost_bsignals.h> #include <boost/bind.hpp> +#include <boost/filesystem.hpp> #include "Swift/Controllers/UIInterfaces/ChatWindow.h" #include <Swiften/Base/Log.h> @@ -25,5 +26,5 @@ FileTransferController::FileTransferController(const JID& receipient, const std: FileTransferController::FileTransferController(IncomingFileTransfer::ref transfer) : - sending(false), otherParty(transfer->getSender()), filename(transfer->filename), transfer(transfer), ftManager(0), ftProgressInfo(0), chatWindow(0), currentState(FileTransfer::State::WaitingForStart) { + sending(false), otherParty(transfer->getSender()), filename(transfer->getFileName()), transfer(transfer), ftManager(0), ftProgressInfo(0), chatWindow(0), currentState(FileTransfer::State::WaitingForStart) { } @@ -42,5 +43,5 @@ std::string FileTransferController::setChatWindow(ChatWindow* wnd, std::string n uiID = wnd->addFileTransfer(QT_TRANSLATE_NOOP("", "me"), true, filename, boost::filesystem::file_size(boost::filesystem::path(filename))); } else { - uiID = wnd->addFileTransfer(nickname, false, filename, transfer->fileSizeInBytes); + uiID = wnd->addFileTransfer(nickname, false, filename, transfer->getFileSizeInBytes()); } return uiID; @@ -65,5 +66,5 @@ int FileTransferController::getProgress() const { boost::uintmax_t FileTransferController::getSize() const { if (transfer) { - return transfer->fileSizeInBytes; + return transfer->getFileSizeInBytes(); } else { return 0; @@ -76,7 +77,7 @@ void FileTransferController::start(std::string& description) { OutgoingFileTransfer::ref outgoingTransfer = ftManager->createOutgoingFileTransfer(otherParty, boost::filesystem::path(filename), description, fileReadStream); if (outgoingTransfer) { - ftProgressInfo = new FileTransferProgressInfo(outgoingTransfer->fileSizeInBytes); + ftProgressInfo = new FileTransferProgressInfo(outgoingTransfer->getFileSizeInBytes()); ftProgressInfo->onProgressPercentage.connect(boost::bind(&FileTransferController::handleProgressPercentageChange, this, _1)); - outgoingTransfer->onStateChange.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); + outgoingTransfer->onStateChanged.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); outgoingTransfer->onProcessedBytes.connect(boost::bind(&FileTransferProgressInfo::setBytesProcessed, ftProgressInfo, _1)); outgoingTransfer->start(); @@ -93,7 +94,7 @@ void FileTransferController::accept(std::string& file) { fileWriteStream = boost::make_shared<FileWriteBytestream>(boost::filesystem::path(file)); - ftProgressInfo = new FileTransferProgressInfo(transfer->fileSizeInBytes); + ftProgressInfo = new FileTransferProgressInfo(transfer->getFileSizeInBytes()); ftProgressInfo->onProgressPercentage.connect(boost::bind(&FileTransferController::handleProgressPercentageChange, this, _1)); - transfer->onStateChange.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); + transfer->onStateChanged.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); transfer->onProcessedBytes.connect(boost::bind(&FileTransferProgressInfo::setBytesProcessed, ftProgressInfo, _1)); incomingTransfer->accept(fileWriteStream); @@ -114,5 +115,8 @@ void FileTransferController::handleFileTransferStateChange(FileTransfer::State s currentState = state; onStateChage(); - switch(state.state) { + switch(state.type) { + case FileTransfer::State::Initial: + assert(false); + return; case FileTransfer::State::Negotiating: chatWindow->setFileTransferStatus(uiID, ChatWindow::Negotiating); @@ -139,5 +143,5 @@ void FileTransferController::handleFileTransferStateChange(FileTransfer::State s return; } - std::cerr << "Unhandled FileTransfer::State!" << std::endl; + assert(false); } diff --git a/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp index 6d19fa1..3081f71 100644 --- a/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp +++ b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp @@ -7,4 +7,6 @@ #include "FileTransferProgressInfo.h" +#include <boost/numeric/conversion/cast.hpp> + #include <Swiften/Base/Log.h> @@ -17,5 +19,5 @@ FileTransferProgressInfo::FileTransferProgressInfo(boost::uintmax_t completeByte void FileTransferProgressInfo::setBytesProcessed(int processedBytes) { int oldPercentage = int(double(completedBytes) / double(completeBytes) * 100.0); - completedBytes += processedBytes; + completedBytes += boost::numeric_cast<boost::uintmax_t>(processedBytes); int newPercentage = int(double(completedBytes) / double(completeBytes) * 100.0); if (oldPercentage != newPercentage) { diff --git a/Swift/Controllers/HighlightAction.cpp b/Swift/Controllers/HighlightAction.cpp new file mode 100644 index 0000000..492d4d2 --- /dev/null +++ b/Swift/Controllers/HighlightAction.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/Controllers/HighlightAction.h> + +namespace Swift { + +void HighlightAction::setHighlightAllText(bool highlightText) +{ + highlightText_ = highlightText; + if (!highlightText_) { + textColor_.clear(); + textBackground_.clear(); + } +} + +void HighlightAction::setPlaySound(bool playSound) +{ + playSound_ = playSound; + if (!playSound_) { + soundFile_.clear(); + } +} + +} diff --git a/Swift/Controllers/HighlightAction.h b/Swift/Controllers/HighlightAction.h new file mode 100644 index 0000000..90768a7 --- /dev/null +++ b/Swift/Controllers/HighlightAction.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> + +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.hpp> + +namespace Swift { + + class HighlightRule; + + class HighlightAction { + public: + HighlightAction() : highlightText_(false), playSound_(false) {} + + /** + * Gets the flag that indicates the entire message should be highlighted. + */ + bool highlightAllText() const { return highlightText_; } + void setHighlightAllText(bool highlightText); + + /** + * Gets the foreground highlight color. + */ + const std::string& getTextColor() const { return textColor_; } + void setTextColor(const std::string& textColor) { textColor_ = textColor; } + + /** + * Gets the background highlight color. + */ + const std::string& getTextBackground() const { return textBackground_; } + void setTextBackground(const std::string& textBackground) { textBackground_ = textBackground; } + + bool playSound() const { return playSound_; } + void setPlaySound(bool playSound); + + /** + * Gets the sound filename. If the string is empty, assume a default sound file. + */ + const std::string& getSoundFile() const { return soundFile_; } + void setSoundFile(const std::string& soundFile) { soundFile_ = soundFile; } + + bool isEmpty() const { return !highlightText_ && !playSound_; } + + private: + friend class boost::serialization::access; + template<class Archive> void serialize(Archive & ar, const unsigned int version); + + bool highlightText_; + std::string textColor_; + std::string textBackground_; + + bool playSound_; + std::string soundFile_; + }; + + template<class Archive> + void HighlightAction::serialize(Archive& ar, const unsigned int /*version*/) + { + ar & highlightText_; + ar & textColor_; + ar & textBackground_; + ar & playSound_; + ar & soundFile_; + } + +} diff --git a/Swift/Controllers/HighlightEditorController.cpp b/Swift/Controllers/HighlightEditorController.cpp new file mode 100644 index 0000000..efa3ba2 --- /dev/null +++ b/Swift/Controllers/HighlightEditorController.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <boost/bind.hpp> + +#include <Swift/Controllers/HighlightEditorController.h> +#include <Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h> +#include <Swift/Controllers/ContactSuggester.h> + +namespace Swift { + +HighlightEditorController::HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWindowFactory* highlightEditorWindowFactory, HighlightManager* highlightManager) +: highlightEditorWindowFactory_(highlightEditorWindowFactory), highlightEditorWindow_(NULL), highlightManager_(highlightManager), contactSuggester_(0) +{ + uiEventStream->onUIEvent.connect(boost::bind(&HighlightEditorController::handleUIEvent, this, _1)); +} + +HighlightEditorController::~HighlightEditorController() +{ + delete highlightEditorWindow_; + highlightEditorWindow_ = NULL; +} + +void HighlightEditorController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) +{ + boost::shared_ptr<RequestHighlightEditorUIEvent> event = boost::dynamic_pointer_cast<RequestHighlightEditorUIEvent>(rawEvent); + if (event) { + if (!highlightEditorWindow_) { + highlightEditorWindow_ = highlightEditorWindowFactory_->createHighlightEditorWindow(); + highlightEditorWindow_->setHighlightManager(highlightManager_); + highlightEditorWindow_->onContactSuggestionsRequested.connect(boost::bind(&HighlightEditorController::handleContactSuggestionsRequested, this, _1)); + } + highlightEditorWindow_->show(); + } +} + +void HighlightEditorController::handleContactSuggestionsRequested(const std::string& text) +{ + if (contactSuggester_) { + highlightEditorWindow_->setContactSuggestions(contactSuggester_->getSuggestions(text, true)); + } +} + +} diff --git a/Swift/Controllers/HighlightEditorController.h b/Swift/Controllers/HighlightEditorController.h new file mode 100644 index 0000000..54322e2 --- /dev/null +++ b/Swift/Controllers/HighlightEditorController.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + + class UIEventStream; + + class HighlightEditorWindowFactory; + class HighlightEditorWindow; + + class HighlightManager; + class ContactSuggester; + + class HighlightEditorController { + public: + HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWindowFactory* highlightEditorWindowFactory, HighlightManager* highlightManager); + ~HighlightEditorController(); + + HighlightManager* getHighlightManager() const { return highlightManager_; } + void setContactSuggester(ContactSuggester *suggester) { contactSuggester_ = suggester; } + + private: + void handleUIEvent(boost::shared_ptr<UIEvent> event); + void handleContactSuggestionsRequested(const std::string& text); + + private: + HighlightEditorWindowFactory* highlightEditorWindowFactory_; + HighlightEditorWindow* highlightEditorWindow_; + HighlightManager* highlightManager_; + ContactSuggester* contactSuggester_; + }; + +} diff --git a/Swift/Controllers/HighlightManager.cpp b/Swift/Controllers/HighlightManager.cpp new file mode 100644 index 0000000..ca0567e --- /dev/null +++ b/Swift/Controllers/HighlightManager.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cassert> + +#include <boost/algorithm/string.hpp> +#include <boost/regex.hpp> +#include <boost/bind.hpp> +#include <boost/numeric/conversion/cast.hpp> +#include <boost/serialization/vector.hpp> +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swift/Controllers/HighlightManager.h> +#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/SettingConstants.h> + +/* How does highlighting work? + * + * HighlightManager manages a list of if-then rules used to highlight messages. + * Rule is represented by HighlightRule. Action ("then" part) is HighlightAction. + * + * + * HighlightManager is also used as a factory for Highlighter objects. + * Each ChatControllerBase has its own Highlighter. + * Highligher may be customized by using setNick(), etc. + * + * ChatControllerBase passes incoming messages to Highlighter and gets HighlightAction in return + * (first matching rule is returned). + * If needed, HighlightAction is then passed back to Highlighter for further handling. + * This results in HighlightManager emiting onHighlight event, + * which is handled by SoundController to play sound notification + */ + +namespace Swift { + +HighlightManager::HighlightManager(SettingsProvider* settings) + : settings_(settings) + , storingSettings_(false) +{ + rules_ = boost::make_shared<HighlightRulesList>(); + loadSettings(); + settings_->onSettingChanged.connect(boost::bind(&HighlightManager::handleSettingChanged, this, _1)); +} + +void HighlightManager::handleSettingChanged(const std::string& settingPath) +{ + if (!storingSettings_ && SettingConstants::HIGHLIGHT_RULES.getKey() == settingPath) { + loadSettings(); + } +} + +std::string HighlightManager::rulesToString() const +{ + std::stringstream stream; + boost::archive::text_oarchive archive(stream); + archive << rules_->list_; + return stream.str(); +} + +std::vector<HighlightRule> HighlightManager::getDefaultRules() +{ + std::vector<HighlightRule> rules; + HighlightRule r; + r.setMatchChat(true); + r.getAction().setPlaySound(true); + rules.push_back(r); + return rules; +} + +HighlightRule HighlightManager::getRule(int index) const +{ + assert(index >= 0 && static_cast<size_t>(index) < rules_->getSize()); + return rules_->getRule(static_cast<size_t>(index)); +} + +void HighlightManager::setRule(int index, const HighlightRule& rule) +{ + assert(index >= 0 && static_cast<size_t>(index) < rules_->getSize()); + rules_->list_[static_cast<size_t>(index)] = rule; +} + +void HighlightManager::insertRule(int index, const HighlightRule& rule) +{ + assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) <= rules_->getSize()); + rules_->list_.insert(rules_->list_.begin() + index, rule); +} + +void HighlightManager::removeRule(int index) +{ + assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) < rules_->getSize()); + rules_->list_.erase(rules_->list_.begin() + index); +} + +void HighlightManager::swapRules(const size_t first, const size_t second) { + assert(first < rules_->getSize()); + assert(second < rules_->getSize()); + const HighlightRule swap = rules_->getRule(first); + rules_->setRule(first, rules_->getRule(second)); + rules_->setRule(second, swap); +} + +void HighlightManager::storeSettings() +{ + storingSettings_ = true; // don't reload settings while saving + settings_->storeSetting(SettingConstants::HIGHLIGHT_RULES, rulesToString()); + storingSettings_ = false; +} + +void HighlightManager::loadSettings() +{ + std::string rulesString = settings_->getSetting(SettingConstants::HIGHLIGHT_RULES); + std::stringstream stream; + stream << rulesString; + try { + boost::archive::text_iarchive archive(stream); + archive >> rules_->list_; + } catch (boost::archive::archive_exception&) { + rules_->list_ = getDefaultRules(); + } +} + +Highlighter* HighlightManager::createHighlighter() +{ + return new Highlighter(this); +} + +} diff --git a/Swift/Controllers/HighlightManager.h b/Swift/Controllers/HighlightManager.h new file mode 100644 index 0000000..07a3fe3 --- /dev/null +++ b/Swift/Controllers/HighlightManager.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> +#include <string> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swift/Controllers/HighlightRule.h> + +namespace Swift { + + class SettingsProvider; + class Highlighter; + + class HighlightManager { + public: + + class HighlightRulesList { + public: + friend class HighlightManager; + size_t getSize() const { return list_.size(); } + const HighlightRule& getRule(const size_t index) const { return list_[index]; } + void addRule(const HighlightRule& rule) { list_.push_back(rule); } + void combineRules(const HighlightRulesList& rhs) { + list_.insert(list_.end(), rhs.list_.begin(), rhs.list_.end()); + } + void setRule(const size_t index, const HighlightRule& rule) { + list_[index] = rule; + } + private: + std::vector<HighlightRule> list_; + }; + + HighlightManager(SettingsProvider* settings); + + Highlighter* createHighlighter(); + + boost::shared_ptr<const HighlightManager::HighlightRulesList> getRules() const { return rules_; } + + HighlightRule getRule(int index) const; + void setRule(int index, const HighlightRule& rule); + void insertRule(int index, const HighlightRule& rule); + void removeRule(int index); + void swapRules(const size_t first, const size_t second); + void storeSettings(); + void loadSettings(); + + boost::signal<void (const HighlightAction&)> onHighlight; + + private: + void handleSettingChanged(const std::string& settingPath); + + std::string rulesToString() const; + static std::vector<HighlightRule> getDefaultRules(); + + SettingsProvider* settings_; + bool storingSettings_; + + boost::shared_ptr<HighlightManager::HighlightRulesList> rules_; + }; + + typedef boost::shared_ptr<const HighlightManager::HighlightRulesList> HighlightRulesListPtr; + +} diff --git a/Swift/Controllers/HighlightRule.cpp b/Swift/Controllers/HighlightRule.cpp new file mode 100644 index 0000000..3251067 --- /dev/null +++ b/Swift/Controllers/HighlightRule.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <algorithm> +#include <boost/algorithm/string.hpp> +#include <boost/lambda/lambda.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/Regex.h> +#include <Swift/Controllers/HighlightRule.h> + +namespace Swift { + +HighlightRule::HighlightRule() + : nickIsKeyword_(false) + , matchCase_(false) + , matchWholeWords_(false) + , matchChat_(false) + , matchMUC_(false) +{ +} + +boost::regex HighlightRule::regexFromString(const std::string & s) const +{ + std::string escaped = Regex::escape(s); + std::string word = matchWholeWords_ ? "\\b" : ""; + boost::regex::flag_type flags = boost::regex::normal; + if (!matchCase_) { + flags |= boost::regex::icase; + } + return boost::regex(word + escaped + word, flags); +} + +void HighlightRule::updateRegex() const +{ + keywordRegex_.clear(); + foreach (const std::string & k, keywords_) { + keywordRegex_.push_back(regexFromString(k)); + } + senderRegex_.clear(); + foreach (const std::string & s, senders_) { + senderRegex_.push_back(regexFromString(s)); + } +} + +std::string HighlightRule::boolToString(bool b) +{ + return b ? "1" : "0"; +} + +bool HighlightRule::boolFromString(const std::string& s) +{ + return s == "1"; +} + +bool HighlightRule::isMatch(const std::string& body, const std::string& sender, const std::string& nick, MessageType messageType) const +{ + if ((messageType == HighlightRule::ChatMessage && matchChat_) || (messageType == HighlightRule::MUCMessage && matchMUC_)) { + + bool matchesKeyword = keywords_.empty() && (nick.empty() || !nickIsKeyword_); + bool matchesSender = senders_.empty(); + + if (!matchesKeyword && nickIsKeyword_ && !nick.empty()) { + if (boost::regex_search(body, regexFromString(nick))) { + matchesKeyword = true; + } + } + + foreach (const boost::regex & rx, senderRegex_) { + if (boost::regex_search(sender, rx)) { + matchesSender = true; + break; + } + } + + if (matchesKeyword && matchesSender) { + return true; + } + } + + return false; +} + +void HighlightRule::setSenders(const std::vector<std::string>& senders) +{ + senders_ = senders; + updateRegex(); +} + +void HighlightRule::setKeywords(const std::vector<std::string>& keywords) +{ + keywords_ = keywords; + updateRegex(); +} + +std::vector<boost::regex> HighlightRule::getKeywordRegex(const std::string& nick) const { + if (nickIsKeyword_) { + std::vector<boost::regex> regex; + if (!nick.empty()) { + regex.push_back(regexFromString(nick)); + } + return regex; + } else { + return keywordRegex_; + } +} + +void HighlightRule::setNickIsKeyword(bool nickIsKeyword) +{ + nickIsKeyword_ = nickIsKeyword; + updateRegex(); +} + +void HighlightRule::setMatchCase(bool matchCase) +{ + matchCase_ = matchCase; + updateRegex(); +} + +void HighlightRule::setMatchWholeWords(bool matchWholeWords) +{ + matchWholeWords_ = matchWholeWords; + updateRegex(); +} + +void HighlightRule::setMatchChat(bool matchChat) +{ + matchChat_ = matchChat; + updateRegex(); +} + +void HighlightRule::setMatchMUC(bool matchMUC) +{ + matchMUC_ = matchMUC; + updateRegex(); +} + +bool HighlightRule::isEmpty() const +{ + return senders_.empty() && keywords_.empty() && !nickIsKeyword_ && !matchChat_ && !matchMUC_ && action_.isEmpty(); +} + +} diff --git a/Swift/Controllers/HighlightRule.h b/Swift/Controllers/HighlightRule.h new file mode 100644 index 0000000..c0226bc --- /dev/null +++ b/Swift/Controllers/HighlightRule.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> +#include <string> + +#include <boost/regex.hpp> +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.hpp> + +#include <Swift/Controllers/HighlightAction.h> + +namespace Swift { + + class HighlightRule { + public: + HighlightRule(); + + enum MessageType { ChatMessage, MUCMessage }; + + bool isMatch(const std::string& body, const std::string& sender, const std::string& nick, MessageType) const; + + const HighlightAction& getAction() const { return action_; } + HighlightAction& getAction() { return action_; } + + const std::vector<std::string>& getSenders() const { return senders_; } + void setSenders(const std::vector<std::string>&); + const std::vector<boost::regex>& getSenderRegex() const { return senderRegex_; } + + const std::vector<std::string>& getKeywords() const { return keywords_; } + void setKeywords(const std::vector<std::string>&); + std::vector<boost::regex> getKeywordRegex(const std::string& nick) const; + + bool getNickIsKeyword() const { return nickIsKeyword_; } + void setNickIsKeyword(bool); + + bool getMatchCase() const { return matchCase_; } + void setMatchCase(bool); + + bool getMatchWholeWords() const { return matchWholeWords_; } + void setMatchWholeWords(bool); + + bool getMatchChat() const { return matchChat_; } + void setMatchChat(bool); + + bool getMatchMUC() const { return matchMUC_; } + void setMatchMUC(bool); + + bool isEmpty() const; + + private: + friend class boost::serialization::access; + template<class Archive> void serialize(Archive & ar, const unsigned int version); + + static std::string boolToString(bool); + static bool boolFromString(const std::string&); + + std::vector<std::string> senders_; + std::vector<std::string> keywords_; + bool nickIsKeyword_; + + mutable std::vector<boost::regex> senderRegex_; + mutable std::vector<boost::regex> keywordRegex_; + void updateRegex() const; + boost::regex regexFromString(const std::string&) const; + + bool matchCase_; + bool matchWholeWords_; + + bool matchChat_; + bool matchMUC_; + + HighlightAction action_; + }; + + template<class Archive> + void HighlightRule::serialize(Archive& ar, const unsigned int /*version*/) + { + ar & senders_; + ar & keywords_; + ar & nickIsKeyword_; + ar & matchChat_; + ar & matchMUC_; + ar & matchCase_; + ar & matchWholeWords_; + ar & action_; + updateRegex(); + } + +} diff --git a/Swift/Controllers/Highlighter.cpp b/Swift/Controllers/Highlighter.cpp new file mode 100644 index 0000000..efeeb6b --- /dev/null +++ b/Swift/Controllers/Highlighter.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/Base/foreach.h> +#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/HighlightManager.h> + +namespace Swift { + +Highlighter::Highlighter(HighlightManager* manager) + : manager_(manager) +{ + setMode(ChatMode); +} + +void Highlighter::setMode(Mode mode) +{ + mode_ = mode; + messageType_ = mode_ == ChatMode ? HighlightRule::ChatMessage : HighlightRule::MUCMessage; +} + +HighlightAction Highlighter::findAction(const std::string& body, const std::string& sender) const +{ + HighlightRulesListPtr rules = manager_->getRules(); + for (size_t i = 0; i < rules->getSize(); ++i) { + const HighlightRule& rule = rules->getRule(i); + if (rule.isMatch(body, sender, nick_, messageType_)) { + return rule.getAction(); + } + } + + return HighlightAction(); +} + +void Highlighter::handleHighlightAction(const HighlightAction& action) +{ + manager_->onHighlight(action); +} + +} diff --git a/Swift/Controllers/Highlighter.h b/Swift/Controllers/Highlighter.h new file mode 100644 index 0000000..d5d846b --- /dev/null +++ b/Swift/Controllers/Highlighter.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> + +#include <Swift/Controllers/HighlightRule.h> + +namespace Swift { + + class HighlightManager; + + class Highlighter { + public: + Highlighter(HighlightManager* manager); + + enum Mode { ChatMode, MUCMode }; + void setMode(Mode mode); + + void setNick(const std::string& nick) { nick_ = nick; } + std::string getNick() const { return nick_; } + + HighlightAction findAction(const std::string& body, const std::string& sender) const; + + void handleHighlightAction(const HighlightAction& action); + + private: + HighlightManager* manager_; + Mode mode_; + HighlightRule::MessageType messageType_; + std::string nick_; + }; + +} diff --git a/Swift/Controllers/HistoryController.cpp b/Swift/Controllers/HistoryController.cpp new file mode 100644 index 0000000..5732382 --- /dev/null +++ b/Swift/Controllers/HistoryController.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + + +#include <Swift/Controllers/HistoryController.h> +#include <Swiften/History/HistoryStorage.h> +#include <Swiften/History/HistoryMessage.h> +#include <boost/date_time/c_local_time_adjustor.hpp> + +namespace Swift { + +HistoryController::HistoryController(HistoryStorage* localHistoryStorage) : localHistory_(localHistoryStorage), remoteArchiveSupported_(false) { +} + +HistoryController::~HistoryController() { +} + +void HistoryController::addMessage(const std::string& message, const JID& fromJID, const JID& toJID, HistoryMessage::Type type, const boost::posix_time::ptime& timeStamp) { + // note: using localtime timestamps + boost::posix_time::ptime localTime = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::utc_to_local(timeStamp); + int offset = (localTime - timeStamp).hours(); + + HistoryMessage historyMessage(message, fromJID, toJID, type, localTime, offset); + + localHistory_->addMessage(historyMessage); + onNewMessage(historyMessage); +} + +std::vector<HistoryMessage> HistoryController::getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const { + return localHistory_->getMessagesFromDate(selfJID, contactJID, type, date); +} + +std::vector<HistoryMessage> HistoryController::getMUCContext(const JID& selfJID, const JID& mucJID, const boost::posix_time::ptime& timeStamp) const { + boost::posix_time::ptime localTime = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::utc_to_local(timeStamp); + return getMessagesFromDate(selfJID, mucJID, HistoryMessage::Groupchat, localTime.date()); +} + +std::vector<HistoryMessage> HistoryController::getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const { + return localHistory_->getMessagesFromPreviousDate(selfJID, contactJID, type, date); +} + +std::vector<HistoryMessage> HistoryController::getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const { + return localHistory_->getMessagesFromNextDate(selfJID, contactJID, type, date); +} + +ContactsMap HistoryController::getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword) const { + return localHistory_->getContacts(selfJID, type, keyword); +} + +boost::posix_time::ptime HistoryController::getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID) { + return localHistory_->getLastTimeStampFromMUC(selfJID, mucJID); +} + +} diff --git a/Swift/Controllers/HistoryController.h b/Swift/Controllers/HistoryController.h new file mode 100644 index 0000000..8c86409 --- /dev/null +++ b/Swift/Controllers/HistoryController.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/JID/JID.h> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <vector> +#include <set> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/History/HistoryMessage.h> +#include <Swiften/History/HistoryStorage.h> + +namespace Swift { + class JID; + + class HistoryController { + public: + HistoryController(HistoryStorage* localHistoryStorage); + ~HistoryController(); + + void addMessage(const std::string& message, const JID& fromJID, const JID& toJID, HistoryMessage::Type type, const boost::posix_time::ptime& timeStamp); + std::vector<HistoryMessage> getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const; + std::vector<HistoryMessage> getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const; + std::vector<HistoryMessage> getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const; + ContactsMap getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword = std::string()) const; + std::vector<HistoryMessage> getMUCContext(const JID& selfJID, const JID& mucJID, const boost::posix_time::ptime& timeStamp) const; + + boost::posix_time::ptime getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID); + + boost::signal<void (const HistoryMessage&)> onNewMessage; + + private: + HistoryStorage* localHistory_; + bool remoteArchiveSupported_; + }; +} diff --git a/Swift/Controllers/HistoryViewController.cpp b/Swift/Controllers/HistoryViewController.cpp new file mode 100644 index 0000000..c24be50 --- /dev/null +++ b/Swift/Controllers/HistoryViewController.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2013 Remko Tronçon + * Licensed under the GNU General Public License. + * See the COPYING file for more information. + */ + +#include <Swift/Controllers/HistoryViewController.h> + +#include <Swiften/Avatars/AvatarManager.h> +#include <Swiften/Base/Path.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/History/HistoryMessage.h> + +#include <Swift/Controllers/HistoryController.h> +#include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h> +#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> +#include <Swift/Controllers/UIEvents/RequestHistoryUIEvent.h> +#include <Swift/Controllers/UIInterfaces/HistoryWindowFactory.h> + +namespace Swift { + static const std::string category[] = { "Contacts", "MUC", "Contacts" }; + +HistoryViewController::HistoryViewController( + const JID& selfJID, + UIEventStream* uiEventStream, + HistoryController* historyController, + NickResolver* nickResolver, + AvatarManager* avatarManager, + PresenceOracle* presenceOracle, + HistoryWindowFactory* historyWindowFactory) : + selfJID_(selfJID), + uiEventStream_(uiEventStream), + historyController_(historyController), + nickResolver_(nickResolver), + avatarManager_(avatarManager), + presenceOracle_(presenceOracle), + historyWindowFactory_(historyWindowFactory), + historyWindow_(NULL), + selectedItem_(NULL), + currentResultDate_(boost::gregorian::not_a_date_time) { + uiEventStream_->onUIEvent.connect(boost::bind(&HistoryViewController::handleUIEvent, this, _1)); + + roster_ = new Roster(false, true); +} + +HistoryViewController::~HistoryViewController() { + uiEventStream_->onUIEvent.disconnect(boost::bind(&HistoryViewController::handleUIEvent, this, _1)); + if (historyWindow_) { + historyWindow_->onSelectedContactChanged.disconnect(boost::bind(&HistoryViewController::handleSelectedContactChanged, this, _1)); + historyWindow_->onReturnPressed.disconnect(boost::bind(&HistoryViewController::handleReturnPressed, this, _1)); + historyWindow_->onScrollReachedTop.disconnect(boost::bind(&HistoryViewController::handleScrollReachedTop, this, _1)); + historyWindow_->onScrollReachedBottom.disconnect(boost::bind(&HistoryViewController::handleScrollReachedBottom, this, _1)); + historyWindow_->onPreviousButtonClicked.disconnect(boost::bind(&HistoryViewController::handlePreviousButtonClicked, this)); + historyWindow_->onNextButtonClicked.disconnect(boost::bind(&HistoryViewController::handleNextButtonClicked, this)); + historyWindow_->onCalendarClicked.disconnect(boost::bind(&HistoryViewController::handleCalendarClicked, this, _1)); + historyController_->onNewMessage.disconnect(boost::bind(&HistoryViewController::handleNewMessage, this, _1)); + + presenceOracle_->onPresenceChange.disconnect(boost::bind(&HistoryViewController::handlePresenceChanged, this, _1)); + avatarManager_->onAvatarChanged.disconnect(boost::bind(&HistoryViewController::handleAvatarChanged, this, _1)); + + delete historyWindow_; + } + delete roster_; +} + +void HistoryViewController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) { + boost::shared_ptr<RequestHistoryUIEvent> event = boost::dynamic_pointer_cast<RequestHistoryUIEvent>(rawEvent); + if (event != NULL) { + if (historyWindow_ == NULL) { + historyWindow_ = historyWindowFactory_->createHistoryWindow(uiEventStream_); + historyWindow_->onSelectedContactChanged.connect(boost::bind(&HistoryViewController::handleSelectedContactChanged, this, _1)); + historyWindow_->onReturnPressed.connect(boost::bind(&HistoryViewController::handleReturnPressed, this, _1)); + historyWindow_->onScrollReachedTop.connect(boost::bind(&HistoryViewController::handleScrollReachedTop, this, _1)); + historyWindow_->onScrollReachedBottom.connect(boost::bind(&HistoryViewController::handleScrollReachedBottom, this, _1)); + historyWindow_->onPreviousButtonClicked.connect(boost::bind(&HistoryViewController::handlePreviousButtonClicked, this)); + historyWindow_->onNextButtonClicked.connect(boost::bind(&HistoryViewController::handleNextButtonClicked, this)); + historyWindow_->onCalendarClicked.connect(boost::bind(&HistoryViewController::handleCalendarClicked, this, _1)); + historyController_->onNewMessage.connect(boost::bind(&HistoryViewController::handleNewMessage, this, _1)); + + presenceOracle_->onPresenceChange.connect(boost::bind(&HistoryViewController::handlePresenceChanged, this, _1)); + avatarManager_->onAvatarChanged.connect(boost::bind(&HistoryViewController::handleAvatarChanged, this, _1)); + + historyWindow_->setRosterModel(roster_); + } + + // populate roster by doing an empty search + handleReturnPressed(std::string()); + + historyWindow_->activate(); + } +} + +void HistoryViewController::handleSelectedContactChanged(RosterItem* newContact) { + // FIXME: signal is triggerd twice. + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(newContact); + + if (contact && selectedItem_ != contact) { + selectedItem_ = contact; + historyWindow_->resetConversationView(); + } + else { + return; + } + + JID contactJID = contact->getJID(); + + std::vector<HistoryMessage> messages; + for (int it = HistoryMessage::Chat; it <= HistoryMessage::PrivateMessage; it++) { + HistoryMessage::Type type = static_cast<HistoryMessage::Type>(it); + + if (contacts_[type].count(contactJID)) { + currentResultDate_ = *contacts_[type][contactJID].rbegin(); + selectedItemType_ = type; + messages = historyController_->getMessagesFromDate(selfJID_, contactJID, type, currentResultDate_); + } + } + + historyWindow_->setDate(currentResultDate_); + + foreach (const HistoryMessage& message, messages) { + addNewMessage(message, false); + } +} + +void HistoryViewController::handleNewMessage(const HistoryMessage& message) { + JID contactJID = message.getFromJID().toBare() == selfJID_ ? message.getToJID() : message.getFromJID(); + + JID displayJID; + if (message.getType() == HistoryMessage::PrivateMessage) { + displayJID = contactJID; + } + else { + displayJID = contactJID.toBare(); + } + + // check current conversation + if (selectedItem_ && selectedItem_->getJID() == displayJID) { + if (historyWindow_->getLastVisibleDate() == message.getTime().date()) { + addNewMessage(message, false); + } + } + + // check if the new message matches the query + if (message.getMessage().find(historyWindow_->getSearchBoxText()) == std::string::npos) { + return; + } + + // update contacts + if (!contacts_[message.getType()].count(displayJID)) { + roster_->addContact(displayJID, displayJID, nickResolver_->jidToNick(displayJID), category[message.getType()], avatarManager_->getAvatarPath(displayJID)); + } + + contacts_[message.getType()][displayJID].insert(message.getTime().date()); +} + +void HistoryViewController::addNewMessage(const HistoryMessage& message, bool addAtTheTop) { + bool senderIsSelf = message.getFromJID().toBare() == selfJID_; + std::string avatarPath = pathToString(avatarManager_->getAvatarPath(message.getFromJID())); + + std::string nick = message.getType() != HistoryMessage::Groupchat ? nickResolver_->jidToNick(message.getFromJID()) : message.getFromJID().getResource(); + historyWindow_->addMessage(message.getMessage(), nick, senderIsSelf, avatarPath, message.getTime(), addAtTheTop); +} + +void HistoryViewController::handleReturnPressed(const std::string& keyword) { + reset(); + + for (int it = HistoryMessage::Chat; it <= HistoryMessage::PrivateMessage; it++) { + HistoryMessage::Type type = static_cast<HistoryMessage::Type>(it); + + contacts_[type] = historyController_->getContacts(selfJID_, type, keyword); + + for (ContactsMap::const_iterator contact = contacts_[type].begin(); contact != contacts_[type].end(); contact++) { + const JID& jid = contact->first; + std::string nick; + if (type == HistoryMessage::PrivateMessage) { + nick = jid.toString(); + } + else { + nick = nickResolver_->jidToNick(jid); + } + roster_->addContact(jid, jid, nick, category[type], avatarManager_->getAvatarPath(jid)); + + Presence::ref presence = getPresence(jid, type == HistoryMessage::Groupchat); + + if (presence.get()) { + roster_->applyOnItem(SetPresence(presence, JID::WithoutResource), jid); + } + } + } +} + +void HistoryViewController::handleScrollReachedTop(const boost::gregorian::date& date) { + if (!selectedItem_) { + return; + } + + std::vector<HistoryMessage> messages = historyController_->getMessagesFromPreviousDate(selfJID_, selectedItem_->getJID(), selectedItemType_, date); + + foreach (const HistoryMessage& message, messages) { + addNewMessage(message, true); + } + historyWindow_->resetConversationViewTopInsertPoint(); +} + +void HistoryViewController::handleScrollReachedBottom(const boost::gregorian::date& date) { + if (!selectedItem_) { + return; + } + + std::vector<HistoryMessage> messages = historyController_->getMessagesFromNextDate(selfJID_, selectedItem_->getJID(), selectedItemType_, date); + + foreach (const HistoryMessage& message, messages) { + addNewMessage(message, false); + } +} + +void HistoryViewController::handleNextButtonClicked() { + if (!selectedItem_) { + return; + } + + std::set<boost::gregorian::date>::iterator date = contacts_[selectedItemType_][selectedItem_->getJID()].find(currentResultDate_); + + if (*date == *contacts_[selectedItemType_][selectedItem_->getJID()].rbegin()) { + return; + } + + historyWindow_->resetConversationView(); + currentResultDate_ = *(++date); + std::vector<HistoryMessage> messages = historyController_->getMessagesFromDate(selfJID_, selectedItem_->getJID(), selectedItemType_, currentResultDate_); + historyWindow_->setDate(currentResultDate_); + + foreach (const HistoryMessage& message, messages) { + addNewMessage(message, false); + } +} + +void HistoryViewController::handlePreviousButtonClicked() { + if (!selectedItem_) { + return; + } + + std::set<boost::gregorian::date>::iterator date = contacts_[selectedItemType_][selectedItem_->getJID()].find(currentResultDate_); + + if (date == contacts_[selectedItemType_][selectedItem_->getJID()].begin()) { + return; + } + + historyWindow_->resetConversationView(); + currentResultDate_ = *(--date); + std::vector<HistoryMessage> messages = historyController_->getMessagesFromDate(selfJID_, selectedItem_->getJID(), selectedItemType_, currentResultDate_); + historyWindow_->setDate(currentResultDate_); + + foreach (const HistoryMessage& message, messages) { + addNewMessage(message, false); + } +} + +void HistoryViewController::reset() { + roster_->removeAll(); + contacts_.clear(); + selectedItem_ = NULL; + historyWindow_->resetConversationView(); +} + +void HistoryViewController::handleCalendarClicked(const boost::gregorian::date& date) { + if (!selectedItem_) { + return; + } + + boost::gregorian::date newDate; + if (contacts_[selectedItemType_][selectedItem_->getJID()].count(date)) { + newDate = date; + } + else if (date < currentResultDate_) { + foreach(const boost::gregorian::date& current, contacts_[selectedItemType_][selectedItem_->getJID()]) { + if (current > date) { + newDate = current; + break; + } + } + } + else { + reverse_foreach(const boost::gregorian::date& current, contacts_[selectedItemType_][selectedItem_->getJID()]) { + if (current < date) { + newDate = current; + break; + } + } + } + + historyWindow_->setDate(newDate); + if (newDate == currentResultDate_) { + return; + } + currentResultDate_ = newDate; + historyWindow_->resetConversationView(); + + std::vector<HistoryMessage> messages = historyController_->getMessagesFromDate(selfJID_, selectedItem_->getJID(), selectedItemType_, currentResultDate_); + historyWindow_->setDate(currentResultDate_); + + foreach (const HistoryMessage& message, messages) { + addNewMessage(message, false); + } +} + +void HistoryViewController::handlePresenceChanged(Presence::ref presence) { + JID jid = presence->getFrom(); + + if (contacts_[HistoryMessage::Chat].count(jid.toBare())) { + roster_->applyOnItems(SetPresence(presence, JID::WithoutResource)); + return; + } + + if (contacts_[HistoryMessage::Groupchat].count(jid.toBare())) { + Presence::ref availablePresence = boost::make_shared<Presence>(Presence()); + availablePresence->setFrom(jid.toBare()); + roster_->applyOnItems(SetPresence(availablePresence, JID::WithResource)); + } + + if (contacts_[HistoryMessage::PrivateMessage].count(jid)) { + roster_->applyOnItems(SetPresence(presence, JID::WithResource)); + } +} + +void HistoryViewController::handleAvatarChanged(const JID& jid) { + roster_->applyOnItems(SetAvatar(jid, avatarManager_->getAvatarPath(jid))); +} + +Presence::ref HistoryViewController::getPresence(const JID& jid, bool isMUC) { + if (jid.isBare() && !isMUC) { + return presenceOracle_->getHighestPriorityPresence(jid); + } + + std::vector<Presence::ref> mucPresence = presenceOracle_->getAllPresence(jid.toBare()); + + if (isMUC && !mucPresence.empty()) { + Presence::ref presence = boost::make_shared<Presence>(Presence()); + presence->setFrom(jid); + return presence; + } + + foreach (Presence::ref presence, mucPresence) { + if (presence.get() && presence->getFrom() == jid) { + return presence; + } + } + + return Presence::create(); +} + +} diff --git a/Swift/Controllers/HistoryViewController.h b/Swift/Controllers/HistoryViewController.h new file mode 100644 index 0000000..f44c968 --- /dev/null +++ b/Swift/Controllers/HistoryViewController.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/boost_bsignals.h> +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> + +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/History/HistoryStorage.h> +#include <set> + +namespace Swift { + class HistoryWindowFactory; + class HistoryWindow; + class Roster; + class RosterItem; + class ContactRosterItem; + class HistoryController; + class NickResolver; + class AvatarManager; + + class HistoryViewController { + public: + HistoryViewController(const JID& selfJID, UIEventStream* uiEventStream, HistoryController* historyController, NickResolver* nickResolver, AvatarManager* avatarManager, PresenceOracle* presenceOracle, HistoryWindowFactory* historyWindowFactory); + ~HistoryViewController(); + + private: + void handleUIEvent(boost::shared_ptr<UIEvent> event); + void handleSelectedContactChanged(RosterItem* item); + void handleNewMessage(const HistoryMessage& message); + void handleReturnPressed(const std::string& keyword); + void handleScrollReachedTop(const boost::gregorian::date& date); + void handleScrollReachedBottom(const boost::gregorian::date& date); + void handlePreviousButtonClicked(); + void handleNextButtonClicked(); + void handleCalendarClicked(const boost::gregorian::date& date); + void handlePresenceChanged(Presence::ref presence); + void handleAvatarChanged(const JID& jid); + + void addNewMessage(const HistoryMessage& message, bool addAtTheTop); + void reset(); + Presence::ref getPresence(const JID& jid, bool isMUC); + + private: + JID selfJID_; + UIEventStream* uiEventStream_; + HistoryController* historyController_; + NickResolver* nickResolver_; + AvatarManager* avatarManager_; + PresenceOracle* presenceOracle_; + HistoryWindowFactory* historyWindowFactory_; + HistoryWindow* historyWindow_; + Roster* roster_; + + std::map<HistoryMessage::Type, ContactsMap> contacts_; + ContactRosterItem* selectedItem_; + HistoryMessage::Type selectedItemType_; + boost::gregorian::date currentResultDate_; + }; +} diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 7bd89cb..95d8134 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith and Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,70 +7,85 @@ #include <Swift/Controllers/MainController.h> +#include <cstdlib> + #include <boost/bind.hpp> #include <boost/lexical_cast.hpp> #include <boost/shared_ptr.hpp> #include <boost/smart_ptr/make_shared.hpp> -#include <string> -#include <stdlib.h> #include <Swiften/Base/format.h> #include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/String.h> +#include <Swiften/StringCodecs/Base64.h> +#include <Swiften/Network/TimerFactory.h> +#include <Swiften/Client/Storages.h> +#include <Swiften/VCards/VCardManager.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Client/Client.h> +#include <Swiften/Presence/PresenceSender.h> +#include <Swiften/Elements/ChatState.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/VCardUpdate.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Disco/CapsInfoGenerator.h> +#include <Swiften/Disco/GetDiscoInfoRequest.h> +#include <Swiften/Disco/ClientDiscoManager.h> +#include <Swiften/VCards/GetVCardRequest.h> +#include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/Network/NetworkFactories.h> +#include <Swiften/FileTransfer/FileTransferManager.h> +#include <Swiften/Client/ClientXMLTracer.h> +#include <Swiften/Client/StanzaChannel.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/Crypto/CryptoProvider.h> + +#include <SwifTools/Dock/Dock.h> +#include <SwifTools/Notifier/TogglableNotifier.h> +#include <SwifTools/Idle/IdleDetector.h> + #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/UIInterfaces/UIFactory.h> -#include "Swiften/Network/TimerFactory.h" -#include "Swift/Controllers/BuildVersion.h" -#include "Swiften/Client/Storages.h" -#include "Swiften/VCards/VCardManager.h" -#include "Swift/Controllers/Chat/UserSearchController.h" -#include "Swift/Controllers/Chat/ChatsManager.h" -#include "Swift/Controllers/XMPPEvents/EventController.h" -#include "Swift/Controllers/EventWindowController.h" -#include "Swift/Controllers/UIInterfaces/LoginWindow.h" -#include "Swift/Controllers/UIInterfaces/LoginWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/MainWindow.h" -#include "Swift/Controllers/Chat/MUCController.h" -#include "Swiften/Client/NickResolver.h" -#include "Swift/Controllers/Roster/RosterController.h" -#include "Swift/Controllers/SoundEventController.h" -#include "Swift/Controllers/SoundPlayer.h" -#include "Swift/Controllers/StatusTracker.h" -#include "Swift/Controllers/SystemTray.h" -#include "Swift/Controllers/SystemTrayController.h" -#include "Swift/Controllers/XMLConsoleController.h" -#include "Swift/Controllers/FileTransferListController.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/Controllers/PresenceNotifier.h" -#include "Swift/Controllers/EventNotifier.h" -#include "Swift/Controllers/Storages/StoragesFactory.h" -#include "SwifTools/Dock/Dock.h" -#include "SwifTools/Notifier/TogglableNotifier.h" -#include "Swiften/Base/foreach.h" -#include "Swiften/Client/Client.h" -#include "Swiften/Presence/PresenceSender.h" -#include "Swiften/Elements/ChatState.h" -#include "Swiften/Elements/Presence.h" -#include "Swiften/Elements/VCardUpdate.h" -#include "Swift/Controllers/Settings/SettingsProvider.h" -#include "Swiften/Elements/DiscoInfo.h" -#include "Swiften/Disco/CapsInfoGenerator.h" -#include "Swiften/Disco/GetDiscoInfoRequest.h" -#include "Swiften/Disco/ClientDiscoManager.h" -#include "Swiften/VCards/GetVCardRequest.h" -#include "Swiften/StringCodecs/SHA1.h" -#include "Swiften/StringCodecs/Hexify.h" -#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" -#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" -#include "Swift/Controllers/Storages/CertificateStorageFactory.h" -#include "Swift/Controllers/Storages/CertificateStorageTrustChecker.h" -#include "Swiften/Network/NetworkFactories.h" +#include <Swift/Controllers/BuildVersion.h> +#include <Swift/Controllers/Chat/UserSearchController.h> +#include <Swift/Controllers/Chat/ChatsManager.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/EventWindowController.h> +#include <Swift/Controllers/UIInterfaces/LoginWindow.h> +#include <Swift/Controllers/UIInterfaces/LoginWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/MainWindow.h> +#include <Swift/Controllers/Chat/MUCController.h> +#include <Swift/Controllers/Roster/RosterController.h> +#include <Swift/Controllers/SoundEventController.h> +#include <Swift/Controllers/SoundPlayer.h> +#include <Swift/Controllers/StatusTracker.h> +#include <Swift/Controllers/SystemTray.h> +#include <Swift/Controllers/SystemTrayController.h> +#include <Swift/Controllers/XMLConsoleController.h> +#include <Swift/Controllers/HistoryController.h> +#include <Swift/Controllers/HistoryViewController.h> +#include <Swift/Controllers/FileTransferListController.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/PresenceNotifier.h> +#include <Swift/Controllers/EventNotifier.h> +#include <Swift/Controllers/Storages/StoragesFactory.h> +#include <Swift/Controllers/WhiteboardManager.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/Storages/CertificateStorageFactory.h> +#include <Swift/Controllers/Storages/CertificateStorageTrustChecker.h> #include <Swift/Controllers/ProfileController.h> +#include <Swift/Controllers/ShowProfileController.h> #include <Swift/Controllers/ContactEditController.h> #include <Swift/Controllers/XMPPURIController.h> -#include "Swift/Controllers/AdHocManager.h" -#include <SwifTools/Idle/IdleDetector.h> +#include <Swift/Controllers/AdHocManager.h> #include <Swift/Controllers/FileTransfer/FileTransferOverview.h> -#include <Swiften/FileTransfer/FileTransferManager.h> -#include <Swiften/Client/ClientXMLTracer.h> #include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/HighlightManager.h> +#include <Swift/Controllers/HighlightEditorController.h> +#include <Swift/Controllers/BlockListController.h> +#include <Swift/Controllers/ContactSuggester.h> +#include <Swift/Controllers/ContactsFromXMPPRoster.h> namespace Swift { @@ -93,4 +108,5 @@ MainController::MainController( URIHandler* uriHandler, IdleDetector* idleDetector, + const std::map<std::string, std::string>& emoticons, bool useDelayForLatency) : eventLoop_(eventLoop), @@ -104,5 +120,6 @@ MainController::MainController( loginWindow_(NULL) , useDelayForLatency_(useDelayForLatency), - ftOverview_(NULL) { + ftOverview_(NULL), + emoticons_(emoticons) { storages_ = NULL; certificateStorage_ = NULL; @@ -112,9 +129,18 @@ MainController::MainController( rosterController_ = NULL; chatsManager_ = NULL; + historyController_ = NULL; + historyViewController_ = NULL; eventWindowController_ = NULL; profileController_ = NULL; + blockListController_ = NULL; + showProfileController_ = NULL; contactEditController_ = NULL; userSearchControllerChat_ = NULL; userSearchControllerAdd_ = NULL; + userSearchControllerInvite_ = NULL; + contactsFromRosterProvider_ = NULL; + contactSuggesterWithoutRoster_ = NULL; + contactSuggesterWithRoster_ = NULL; + whiteboardManager_ = NULL; adHocManager_ = NULL; quitRequested_ = false; @@ -134,5 +160,9 @@ MainController::MainController( loginWindow_ = uiFactory_->createLoginWindow(uiEventStream_); loginWindow_->setShowNotificationToggle(!notifier->isExternallyConfigured()); - soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings); + + highlightManager_ = new HighlightManager(settings_); + highlightEditorController_ = new HighlightEditorController(uiEventStream_, uiFactory_, highlightManager_); + + soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings, highlightManager_); xmppURIController_ = new XMPPURIController(uriHandler_, uiEventStream_); @@ -142,4 +172,5 @@ MainController::MainController( std::string cachedPassword; std::string cachedCertificate; + ClientOptions cachedOptions; bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS); if (!eagle) { @@ -149,8 +180,10 @@ MainController::MainController( std::string certificate = profileSettings.getStringSetting("certificate"); std::string jid = profileSettings.getStringSetting("jid"); - loginWindow_->addAvailableAccount(jid, password, certificate); + ClientOptions clientOptions = parseClientOptions(profileSettings.getStringSetting("options")); + loginWindow_->addAvailableAccount(jid, password, certificate, clientOptions); if (jid == selectedLoginJID) { cachedPassword = password; cachedCertificate = certificate; + cachedOptions = clientOptions; } } @@ -160,5 +193,5 @@ MainController::MainController( - loginWindow_->onLoginRequest.connect(boost::bind(&MainController::handleLoginRequest, this, _1, _2, _3, _4, _5, _6)); + loginWindow_->onLoginRequest.connect(boost::bind(&MainController::handleLoginRequest, this, _1, _2, _3, _4, _5, _6, _7)); loginWindow_->onPurgeSavedLoginRequest.connect(boost::bind(&MainController::handlePurgeSavedLoginRequest, this, _1)); loginWindow_->onCancelLoginRequest.connect(boost::bind(&MainController::handleCancelLoginRequest, this)); @@ -177,5 +210,5 @@ MainController::MainController( profileSettings_ = new ProfileSettingsProvider(selectedLoginJID, settings_); /* FIXME: deal with autologin with a cert*/ - handleLoginRequest(selectedLoginJID, cachedPassword, cachedCertificate, CertificateWithKey::ref(), true, true); + handleLoginRequest(selectedLoginJID, cachedPassword, cachedCertificate, CertificateWithKey::ref(), cachedOptions, true, true); } else { profileSettings_ = NULL; @@ -191,4 +224,6 @@ MainController::~MainController() { resetClient(); + delete highlightEditorController_; + delete highlightManager_; delete fileTransferListController_; delete xmlConsoleController_; @@ -214,10 +249,20 @@ void MainController::resetClient() { delete profileController_; profileController_ = NULL; + delete showProfileController_; + showProfileController_ = NULL; delete eventWindowController_; eventWindowController_ = NULL; delete chatsManager_; chatsManager_ = NULL; +#ifdef SWIFT_EXPERIMENTAL_HISTORY + delete historyViewController_; + historyViewController_ = NULL; + delete historyController_; + historyController_ = NULL; +#endif delete ftOverview_; ftOverview_ = NULL; + delete blockListController_; + blockListController_ = NULL; delete rosterController_; rosterController_ = NULL; @@ -238,6 +283,16 @@ void MainController::resetClient() { delete userSearchControllerAdd_; userSearchControllerAdd_ = NULL; + delete userSearchControllerInvite_; + userSearchControllerInvite_ = NULL; + delete contactSuggesterWithoutRoster_; + contactSuggesterWithoutRoster_ = NULL; + delete contactSuggesterWithRoster_; + contactSuggesterWithRoster_ = NULL; + delete contactsFromRosterProvider_; + contactsFromRosterProvider_ = NULL; delete adHocManager_; adHocManager_ = NULL; + delete whiteboardManager_; + whiteboardManager_ = NULL; clientInitialized_ = false; } @@ -278,16 +333,38 @@ void MainController::handleConnected() { if (freshLogin) { profileController_ = new ProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_); - srand(time(NULL)); - int randomPort = 10000 + rand() % 10000; - client_->getFileTransferManager()->startListeningOnPort(randomPort); + showProfileController_ = new ShowProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_); ftOverview_ = new FileTransferOverview(client_->getFileTransferManager()); fileTransferListController_->setFileTransferOverview(ftOverview_); - rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_); + rosterController_ = new RosterController(boundJID_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_, client_->getClientBlockListManager(), client_->getVCardManager()); rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2)); rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this)); + rosterController_->getWindow()->onShowCertificateRequest.connect(boost::bind(&MainController::handleShowCertificateRequest, this)); + + blockListController_ = new BlockListController(client_->getClientBlockListManager(), uiEventStream_, uiFactory_, eventController_); 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(); + + contactSuggesterWithoutRoster_ = new ContactSuggester(); + contactSuggesterWithRoster_ = new ContactSuggester(); - 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_); + userSearchControllerInvite_ = new UserSearchController(UserSearchController::InviteToChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithRoster_, client_->getAvatarManager(), client_->getPresenceOracle()); +#ifdef SWIFT_EXPERIMENTAL_HISTORY + historyController_ = new HistoryController(storages_->getHistoryStorage()); + historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_); + chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_, client_->getVCardManager()); +#else + chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_, client_->getVCardManager()); +#endif + contactsFromRosterProvider_ = new ContactsFromXMPPRoster(client_->getRoster(), client_->getAvatarManager(), client_->getPresenceOracle()); + contactSuggesterWithoutRoster_->addContactProvider(chatsManager_); + contactSuggesterWithRoster_->addContactProvider(chatsManager_); + contactSuggesterWithRoster_->addContactProvider(contactsFromRosterProvider_); + highlightEditorController_->setContactSuggester(contactSuggesterWithoutRoster_); client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); @@ -309,11 +386,16 @@ void MainController::handleConnected() { 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); - 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_); + userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithRoster_, client_->getAvatarManager(), client_->getPresenceOracle()); + userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithoutRoster_, client_->getAvatarManager(), client_->getPresenceOracle()); adHocManager_ = new AdHocManager(JID(boundJID_.getDomain()), uiFactory_, client_->getIQRouter(), uiEventStream_, rosterController_->getWindow()); + + chatsManager_->onImpromptuMUCServiceDiscovered.connect(boost::bind(&UserSearchController::setCanInitiateImpromptuMUC, userSearchControllerChat_, _1)); } loginWindow_->setIsLoggingIn(false); @@ -327,12 +409,16 @@ void MainController::handleConnected() { client_->getVCardManager()->requestOwnVCard(); + rosterController_->setJID(boundJID_); rosterController_->setEnabled(true); + rosterController_->getWindow()->setStreamEncryptionStatus(client_->isStreamEncrypted()); profileController_->setAvailable(true); contactEditController_->setAvailable(true); /* Send presence later to catch all the incoming presences. */ sendPresence(statusTracker_->getNextPresence()); + /* Enable chats last of all, so rejoining MUCs has the right sent presence */ + assert(chatsManager_); chatsManager_->setOnline(true); - + adHocManager_->setOnline(true); } @@ -381,7 +467,5 @@ void MainController::sendPresence(boost::shared_ptr<Presence> presence) { // Add information and send - if (!vCardPhotoHash_.empty()) { presence->updatePayload(boost::make_shared<VCardUpdate>(vCardPhotoHash_)); - } client_->getPresenceSender()->sendPresence(presence); if (presence->getType() == Presence::Unavailable) { @@ -403,5 +487,5 @@ void MainController::handleInputIdleChanged(bool idle) { else { if (idle) { - if (statusTracker_->goAutoAway()) { + if (statusTracker_->goAutoAway(idleDetector_->getIdleTimeSeconds())) { if (client_ && client_->isAvailable()) { sendPresence(statusTracker_->getNextPresence()); @@ -418,5 +502,10 @@ void MainController::handleInputIdleChanged(bool idle) { } -void MainController::handleLoginRequest(const std::string &username, const std::string &password, const std::string& certificatePath, CertificateWithKey::ref certificate, bool remember, bool loginAutomatically) { +void MainController::handleShowCertificateRequest() { + std::vector<Certificate::ref> chain = client_->getStanzaChannel()->getPeerCertificateChain(); + rosterController_->getWindow()->openCertificateDialog(chain); +} + +void MainController::handleLoginRequest(const std::string &username, const std::string &password, const std::string& certificatePath, CertificateWithKey::ref certificate, const ClientOptions& options, bool remember, bool loginAutomatically) { jid_ = JID(username); if (!jid_.isValid() || jid_.getNode().empty()) { @@ -431,11 +520,14 @@ void MainController::handleLoginRequest(const std::string &username, const std:: profileSettings_->storeString("certificate", certificatePath); profileSettings_->storeString("pass", (remember || loginAutomatically) ? password : ""); + std::string optionString = serializeClientOptions(options); + profileSettings_->storeString("options", optionString); settings_->storeSetting(SettingConstants::LAST_LOGIN_JID, username); settings_->storeSetting(SettingConstants::LOGIN_AUTOMATICALLY, loginAutomatically); - loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate")); + loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate"), options); } password_ = password; certificate_ = certificate; + clientOptions_ = options; performLoginFromCachedCredentials(); } @@ -497,12 +589,18 @@ void MainController::performLoginFromCachedCredentials() { rosterController_->getWindow()->setConnecting(); } - ClientOptions clientOptions; + ClientOptions clientOptions = clientOptions_; bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS); clientOptions.forgetPassword = eagle; - clientOptions.useTLS = eagle ? ClientOptions::RequireTLS : ClientOptions::UseTLSWhenAvailable; + clientOptions.useTLS = eagle ? ClientOptions::RequireTLS : clientOptions_.useTLS; client_->connect(clientOptions); } void MainController::handleDisconnected(const boost::optional<ClientError>& error) { + if (rosterController_) { + rosterController_->getWindow()->setStreamEncryptionStatus(false); + } + if (adHocManager_) { + adHocManager_->setOnline(false); + } if (settings_->getSetting(SettingConstants::FORGET_PASSWORDS)) { purgeCachedCredentials(); @@ -552,7 +650,7 @@ void MainController::handleDisconnected(const boost::optional<ClientError>& erro bool forceReconnectAfterCertificateTrust = false; if (!certificateErrorMessage.empty()) { - Certificate::ref certificate = certificateTrustChecker_->getLastCertificate(); - if (loginWindow_->askUserToTrustCertificatePermanently(certificateErrorMessage, certificate)) { - certificateStorage_->addCertificate(certificate); + std::vector<Certificate::ref> certificates = certificateTrustChecker_->getLastCertificateChain(); + if (!certificates.empty() && loginWindow_->askUserToTrustCertificatePermanently(certificateErrorMessage, certificates)) { + certificateStorage_->addCertificate(certificates[0]); forceReconnectAfterCertificateTrust = true; } @@ -662,12 +760,19 @@ void MainController::handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo> chatsManager_->setServerDiscoInfo(info); adHocManager_->setServerDiscoInfo(info); + if (info->hasFeature(DiscoInfo::BlockingCommandFeature)) { + rosterController_->getWindow()->setBlockingCommandAvailable(true); + rosterController_->initBlockingCommand(); + } } } void MainController::handleVCardReceived(const JID& jid, VCard::ref vCard) { - if (!jid.equals(jid_, JID::WithoutResource) || !vCard || vCard->getPhoto().empty()) { + if (!jid.equals(jid_, JID::WithoutResource) || !vCard) { return; } - std::string hash = Hexify::hexify(SHA1::getHash(vCard->getPhoto())); + std::string hash; + if (!vCard->getPhoto().empty()) { + hash = Hexify::hexify(networkFactories_->getCryptoProvider()->getSHA1Hash(vCard->getPhoto())); + } if (hash != vCardPhotoHash_) { vCardPhotoHash_ = hash; @@ -701,3 +806,87 @@ void MainController::handleQuitRequest() { } +#define SERIALIZE_BOOL(option) result += options.option ? "1" : "0"; result += ","; +#define SERIALIZE_INT(option) result += boost::lexical_cast<std::string>(options.option); result += ","; +#define SERIALIZE_STRING(option) result += Base64::encode(createByteArray(options.option)); result += ","; +#define SERIALIZE_SAFE_STRING(option) result += safeByteArrayToString(Base64::encode(options.option)); result += ","; +#define SERIALIZE_URL(option) SERIALIZE_STRING(option.toString()) + +std::string MainController::serializeClientOptions(const ClientOptions& options) { + std::string result; + SERIALIZE_BOOL(useStreamCompression); + switch (options.useTLS) { + case ClientOptions::NeverUseTLS: result += "1";break; + case ClientOptions::UseTLSWhenAvailable: result += "2";break; + case ClientOptions::RequireTLS: result += "3";break; + } + result += ","; + SERIALIZE_BOOL(allowPLAINWithoutTLS); + SERIALIZE_BOOL(useStreamResumption); + SERIALIZE_BOOL(useAcks); + SERIALIZE_STRING(manualHostname); + SERIALIZE_INT(manualPort); + switch (options.proxyType) { + case ClientOptions::NoProxy: result += "1";break; + case ClientOptions::SystemConfiguredProxy: result += "2";break; + case ClientOptions::SOCKS5Proxy: result += "3";break; + case ClientOptions::HTTPConnectProxy: result += "4";break; + } + result += ","; + SERIALIZE_STRING(manualProxyHostname); + SERIALIZE_INT(manualProxyPort); + SERIALIZE_URL(boshURL); + SERIALIZE_URL(boshHTTPConnectProxyURL); + SERIALIZE_SAFE_STRING(boshHTTPConnectProxyAuthID); + SERIALIZE_SAFE_STRING(boshHTTPConnectProxyAuthPassword); + return result; +} + +#define CHECK_PARSE_LENGTH if (i >= segments.size()) {return result;} +#define PARSE_INT_RAW(defaultValue) CHECK_PARSE_LENGTH intVal = defaultValue; try {intVal = boost::lexical_cast<int>(segments[i]);} catch(const boost::bad_lexical_cast&) {};i++; +#define PARSE_STRING_RAW CHECK_PARSE_LENGTH stringVal = byteArrayToString(Base64::decode(segments[i]));i++; + +#define PARSE_BOOL(option, defaultValue) PARSE_INT_RAW(defaultValue); result.option = (intVal == 1); +#define PARSE_INT(option, defaultValue) PARSE_INT_RAW(defaultValue); result.option = intVal; +#define PARSE_STRING(option) PARSE_STRING_RAW; result.option = stringVal; +#define PARSE_SAFE_STRING(option) PARSE_STRING_RAW; result.option = SafeString(createSafeByteArray(stringVal)); +#define PARSE_URL(option) {PARSE_STRING_RAW; result.option = URL::fromString(stringVal);} + + +ClientOptions MainController::parseClientOptions(const std::string& optionString) { + ClientOptions result; + size_t i = 0; + int intVal = 0; + std::string stringVal; + std::vector<std::string> segments = String::split(optionString, ','); + + PARSE_BOOL(useStreamCompression, 1); + PARSE_INT_RAW(-1); + switch (intVal) { + case 1: result.useTLS = ClientOptions::NeverUseTLS;break; + case 2: result.useTLS = ClientOptions::UseTLSWhenAvailable;break; + case 3: result.useTLS = ClientOptions::RequireTLS;break; + default:; + } + PARSE_BOOL(allowPLAINWithoutTLS, 0); + PARSE_BOOL(useStreamResumption, 0); + PARSE_BOOL(useAcks, 1); + PARSE_STRING(manualHostname); + PARSE_INT(manualPort, -1); + PARSE_INT_RAW(-1); + switch (intVal) { + case 1: result.proxyType = ClientOptions::NoProxy;break; + case 2: result.proxyType = ClientOptions::SystemConfiguredProxy;break; + case 3: result.proxyType = ClientOptions::SOCKS5Proxy;break; + case 4: result.proxyType = ClientOptions::HTTPConnectProxy;break; + } + PARSE_STRING(manualProxyHostname); + PARSE_INT(manualProxyPort, -1); + PARSE_URL(boshURL); + PARSE_URL(boshHTTPConnectProxyURL); + PARSE_SAFE_STRING(boshHTTPConnectProxyAuthID); + PARSE_SAFE_STRING(boshHTTPConnectProxyAuthPassword); + + return result; +} + } diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index 14de4eb..6fbde6d 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,22 +7,26 @@ #pragma once -#include <Swiften/Base/boost_bsignals.h> -#include <boost/shared_ptr.hpp> #include <vector> - -#include "Swiften/Network/Timer.h" +#include <map> #include <string> -#include "Swiften/Client/ClientError.h" -#include "Swiften/JID/JID.h" -#include "Swiften/Elements/DiscoInfo.h" -#include "Swiften/Elements/VCard.h" -#include "Swiften/Elements/ErrorPayload.h" -#include "Swiften/Elements/Presence.h" -#include "Swift/Controllers/Settings/SettingsProvider.h" -#include "Swift/Controllers/ProfileSettingsProvider.h" -#include "Swiften/Elements/CapsInfo.h" -#include "Swift/Controllers/XMPPEvents/ErrorEvent.h" -#include "Swift/Controllers/UIEvents/UIEvent.h" -#include "Swiften/Client/ClientXMLTracer.h" + +#include <boost/shared_ptr.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Network/Timer.h> +#include <Swiften/Client/ClientError.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Elements/VCard.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/CapsInfo.h> +#include <Swiften/Client/ClientXMLTracer.h> + +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/ProfileSettingsProvider.h> +#include <Swift/Controllers/XMPPEvents/ErrorEvent.h> +#include <Swift/Controllers/UIEvents/UIEvent.h> + namespace Swift { @@ -44,4 +48,5 @@ namespace Swift { class Notifier; class ProfileController; + class ShowProfileController; class ContactEditController; class TogglableNotifier; @@ -53,4 +58,6 @@ namespace Swift { class SoundPlayer; class XMLConsoleController; + class HistoryViewController; + class HistoryController; class FileTransferListController; class UIEventStream; @@ -69,4 +76,10 @@ namespace Swift { class AdHocCommandWindowFactory; class FileTransferOverview; + class WhiteboardManager; + class HighlightManager; + class HighlightEditorController; + class BlockListController; + class ContactSuggester; + class ContactsFromXMPPRoster; class MainController { @@ -85,4 +98,5 @@ namespace Swift { URIHandler* uriHandler, IdleDetector* idleDetector, + const std::map<std::string, std::string>& emoticons, bool useDelayForLatency); ~MainController(); @@ -92,5 +106,5 @@ namespace Swift { void resetClient(); void handleConnected(); - void handleLoginRequest(const std::string& username, const std::string& password, const std::string& certificatePath, CertificateWithKey::ref certificate, bool remember, bool loginAutomatically); + void handleLoginRequest(const std::string& username, const std::string& password, const std::string& certificatePath, CertificateWithKey::ref certificate, const ClientOptions& options, bool remember, bool loginAutomatically); void handleCancelLoginRequest(); void handleQuitRequest(); @@ -104,4 +118,5 @@ namespace Swift { void sendPresence(boost::shared_ptr<Presence> presence); void handleInputIdleChanged(bool); + void handleShowCertificateRequest(); void logout(); void signOut(); @@ -116,4 +131,6 @@ namespace Swift { void handleForceQuit(); void purgeCachedCredentials(); + std::string serializeClientOptions(const ClientOptions& options); + ClientOptions parseClientOptions(const std::string& optionString); private: @@ -143,8 +160,15 @@ namespace Swift { UIEventStream* uiEventStream_; XMLConsoleController* xmlConsoleController_; + HistoryViewController* historyViewController_; + HistoryController* historyController_; FileTransferListController* fileTransferListController_; + BlockListController* blockListController_; ChatsManager* chatsManager_; ProfileController* profileController_; + ShowProfileController* showProfileController_; ContactEditController* contactEditController_; + ContactsFromXMPPRoster* contactsFromRosterProvider_; + ContactSuggester* contactSuggesterWithoutRoster_; + ContactSuggester* contactSuggesterWithRoster_; JID jid_; JID boundJID_; @@ -155,8 +179,10 @@ namespace Swift { std::string password_; CertificateWithKey::ref certificate_; + ClientOptions clientOptions_; boost::shared_ptr<ErrorEvent> lastDisconnectError_; bool useDelayForLatency_; UserSearchController* userSearchControllerChat_; UserSearchController* userSearchControllerAdd_; + UserSearchController* userSearchControllerInvite_; int timeBeforeNextReconnect_; Timer::ref reconnectTimer_; @@ -167,4 +193,8 @@ namespace Swift { static const int SecondsToWaitBeforeForceQuitting; FileTransferOverview* ftOverview_; + WhiteboardManager* whiteboardManager_; + HighlightManager* highlightManager_; + HighlightEditorController* highlightEditorController_; + std::map<std::string, std::string> emoticons_; }; } diff --git a/Swift/Controllers/ProfileController.cpp b/Swift/Controllers/ProfileController.cpp index 101e283..241cc2e 100644 --- a/Swift/Controllers/ProfileController.cpp +++ b/Swift/Controllers/ProfileController.cpp @@ -26,5 +26,5 @@ ProfileController::~ProfileController() { vcardManager->onOwnVCardChanged.disconnect(boost::bind(&ProfileController::handleOwnVCardChanged, this, _1)); profileWindow->onVCardChangeRequest.disconnect(boost::bind(&ProfileController::handleVCardChangeRequest, this, _1)); - delete profileWindow; + profileWindow->onWindowAboutToBeClosed.disconnect(boost::bind(&ProfileController::handleProfileWindowAboutToBeClosed, this, _1)); } uiEventStream->onUIEvent.disconnect(boost::bind(&ProfileController::handleUIEvent, this, _1)); @@ -38,5 +38,7 @@ void ProfileController::handleUIEvent(UIEvent::ref event) { if (!profileWindow) { profileWindow = profileWindowFactory->createProfileWindow(); + profileWindow->setEditable(true); profileWindow->onVCardChangeRequest.connect(boost::bind(&ProfileController::handleVCardChangeRequest, this, _1)); + profileWindow->onWindowAboutToBeClosed.connect(boost::bind(&ProfileController::handleProfileWindowAboutToBeClosed, this, _1)); vcardManager->onOwnVCardChanged.connect(boost::bind(&ProfileController::handleOwnVCardChanged, this, _1)); } @@ -76,4 +78,8 @@ void ProfileController::handleOwnVCardChanged(VCard::ref vcard) { } +void ProfileController::handleProfileWindowAboutToBeClosed(const JID&) { + profileWindow = NULL; +} + void ProfileController::setAvailable(bool b) { available = b; diff --git a/Swift/Controllers/ProfileController.h b/Swift/Controllers/ProfileController.h index c1afcf9..538df36 100644 --- a/Swift/Controllers/ProfileController.h +++ b/Swift/Controllers/ProfileController.h @@ -30,4 +30,5 @@ namespace Swift { void handleSetVCardResponse(ErrorPayload::ref); void handleOwnVCardChanged(VCard::ref vcard); + void handleProfileWindowAboutToBeClosed(const JID&); void updateDialogStatus(); diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp index 8c388bf..e0651bc 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.cpp +++ b/Swift/Controllers/Roster/ContactRosterItem.cpp @@ -5,13 +5,20 @@ */ -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" +#include <Swift/Controllers/Roster/ContactRosterItem.h> + +#include <boost/date_time/posix_time/posix_time.hpp> #include <Swiften/Base/foreach.h> +#include <Swiften/Base/DateTime.h> +#include <Swiften/Elements/Idle.h> +#include <Swift/Controllers/Intl.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> namespace Swift { -ContactRosterItem::ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent) : RosterItem(name, parent), jid_(jid), displayJID_(displayJID) { +ContactRosterItem::ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent) +: RosterItem(name, parent), jid_(jid), displayJID_(displayJID.toBare()), mucRole_(MUCOccupant::NoRole), mucAffiliation_(MUCOccupant::NoAffiliation), blockState_(BlockingNotSupported) +{ } @@ -25,10 +32,10 @@ StatusShow::Type ContactRosterItem::getStatusShow() const { StatusShow::Type ContactRosterItem::getSimplifiedStatusShow() const { switch (shownPresence_ ? shownPresence_->getShow() : StatusShow::None) { - case StatusShow::Online: return StatusShow::Online; break; - case StatusShow::Away: return StatusShow::Away; break; - case StatusShow::XA: return StatusShow::Away; break; - case StatusShow::FFC: return StatusShow::Online; break; - case StatusShow::DND: return StatusShow::DND; break; - case StatusShow::None: return StatusShow::None; break; + case StatusShow::Online: return StatusShow::Online; + case StatusShow::Away: return StatusShow::Away; + case StatusShow::XA: return StatusShow::Away; + case StatusShow::FFC: return StatusShow::Online; + case StatusShow::DND: return StatusShow::DND; + case StatusShow::None: return StatusShow::None; } assert(false); @@ -40,9 +47,28 @@ std::string ContactRosterItem::getStatusText() const { } -void ContactRosterItem::setAvatarPath(const std::string& path) { +std::string ContactRosterItem::getIdleText() const { + Idle::ref idle = shownPresence_ ? shownPresence_->getPayload<Idle>() : Idle::ref(); + if (!idle || idle->getSince().is_not_a_date_time()) { + return ""; + } else { + return dateTimeToLocalString(idle->getSince()); + } +} + +std::string ContactRosterItem::getOfflineSinceText() const { + if (offlinePresence_) { + boost::optional<boost::posix_time::ptime> delay = offlinePresence_->getTimestamp(); + if (offlinePresence_->getType() == Presence::Unavailable && delay) { + return dateTimeToLocalString(*delay); + } + } + return ""; +} + +void ContactRosterItem::setAvatarPath(const boost::filesystem::path& path) { avatarPath_ = path; onDataChanged(); } -const std::string& ContactRosterItem::getAvatarPath() const { +const boost::filesystem::path& ContactRosterItem::getAvatarPath() const { return avatarPath_; } @@ -114,6 +140,41 @@ void ContactRosterItem::removeGroup(const std::string& group) { } +MUCOccupant::Role ContactRosterItem::getMUCRole() const +{ + return mucRole_; +} + +void ContactRosterItem::setMUCRole(const MUCOccupant::Role& role) +{ + mucRole_ = role; +} + +MUCOccupant::Affiliation ContactRosterItem::getMUCAffiliation() const +{ + return mucAffiliation_; +} + +void ContactRosterItem::setMUCAffiliation(const MUCOccupant::Affiliation& affiliation) +{ + mucAffiliation_ = affiliation; +} + +std::string ContactRosterItem::getMUCAffiliationText() const +{ + std::string affiliationString; + switch (mucAffiliation_) { + case MUCOccupant::Owner: affiliationString = QT_TRANSLATE_NOOP("", "Owner"); break; + case MUCOccupant::Admin: affiliationString = QT_TRANSLATE_NOOP("", "Admin"); break; + case MUCOccupant::Member: affiliationString = QT_TRANSLATE_NOOP("", "Member"); break; + case MUCOccupant::Outcast: affiliationString = QT_TRANSLATE_NOOP("", "Outcast"); break; + case MUCOccupant::NoAffiliation: affiliationString = ""; break; + } + + return affiliationString; +} + void ContactRosterItem::setSupportedFeatures(const std::set<Feature>& features) { features_ = features; + onDataChanged(); } @@ -122,5 +183,21 @@ bool ContactRosterItem::supportsFeature(const Feature feature) const { } +void ContactRosterItem::setBlockState(BlockState state) { + blockState_ = state; + onDataChanged(); } +ContactRosterItem::BlockState ContactRosterItem::blockState() const { + return blockState_; +} + +VCard::ref ContactRosterItem::getVCard() const { + return vcard_; +} +void ContactRosterItem::setVCard(VCard::ref vcard) { + vcard_ = vcard; + onDataChanged(); +} + +} diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h index 9932dc4..ab10c66 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.h +++ b/Swift/Controllers/Roster/ContactRosterItem.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,16 +7,22 @@ #pragma once -#include <string> -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Roster/RosterItem.h" -#include "Swiften/Elements/StatusShow.h" -#include "Swiften/Elements/Presence.h" - #include <map> #include <set> +#include <string> + #include <boost/bind.hpp> -#include "Swiften/Base/boost_bsignals.h" +#include <boost/date_time/posix_time/ptime.hpp> +#include <boost/filesystem/path.hpp> #include <boost/shared_ptr.hpp> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/StatusShow.h> +#include <Swiften/Elements/MUCOccupant.h> +#include <Swiften/Elements/VCard.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/RosterItem.h> + namespace Swift { @@ -26,4 +32,12 @@ class ContactRosterItem : public RosterItem { enum Feature { FileTransferFeature, + WhiteboardFeature + }; + + enum BlockState { + BlockingNotSupported, + IsBlocked, + IsUnblocked, + IsDomainBlocked }; @@ -35,6 +49,8 @@ class ContactRosterItem : public RosterItem { StatusShow::Type getSimplifiedStatusShow() const; std::string getStatusText() const; - void setAvatarPath(const std::string& path); - const std::string& getAvatarPath() const; + std::string getIdleText() const; + std::string getOfflineSinceText() const; + void setAvatarPath(const boost::filesystem::path& path); + const boost::filesystem::path& getAvatarPath() const; const JID& getJID() const; void setDisplayJID(const JID& jid); @@ -48,15 +64,35 @@ class ContactRosterItem : public RosterItem { void removeGroup(const std::string& group); + MUCOccupant::Role getMUCRole() const; + void setMUCRole(const MUCOccupant::Role& role); + MUCOccupant::Affiliation getMUCAffiliation() const; + void setMUCAffiliation(const MUCOccupant::Affiliation& affiliation); + std::string getMUCAffiliationText() const; + void setSupportedFeatures(const std::set<Feature>& features); bool supportsFeature(Feature feature) const; + + void setBlockState(BlockState state); + BlockState blockState() const; + + VCard::ref getVCard() const; + void setVCard(VCard::ref vcard); + + boost::signal<void ()> onVCardRequested; + private: JID jid_; JID displayJID_; - std::string avatarPath_; + boost::posix_time::ptime lastAvailableTime_; + boost::filesystem::path avatarPath_; std::map<std::string, boost::shared_ptr<Presence> > presences_; boost::shared_ptr<Presence> offlinePresence_; boost::shared_ptr<Presence> shownPresence_; std::vector<std::string> groups_; + MUCOccupant::Role mucRole_; + MUCOccupant::Affiliation mucAffiliation_; std::set<Feature> features_; + BlockState blockState_; + VCard::ref vcard_; }; diff --git a/Swift/Controllers/Roster/FuzzyRosterFilter.h b/Swift/Controllers/Roster/FuzzyRosterFilter.h new file mode 100644 index 0000000..6710084 --- /dev/null +++ b/Swift/Controllers/Roster/FuzzyRosterFilter.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> + +#include <Swift/Controllers/ContactSuggester.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/RosterItem.h> +#include <Swift/Controllers/Roster/RosterFilter.h> + +namespace Swift { + +class FuzzyRosterFilter : public RosterFilter { + public: + FuzzyRosterFilter(const std::string& query) : query_(query) { } + virtual ~FuzzyRosterFilter() {} + virtual bool operator() (RosterItem* item) const { + ContactRosterItem *contactItem = dynamic_cast<ContactRosterItem*>(item); + if (contactItem) { + const bool itemMatched = ContactSuggester::fuzzyMatch(contactItem->getDisplayName(), query_) || ContactSuggester::fuzzyMatch(contactItem->getDisplayJID(), query_); + return !itemMatched; + } else { + return false; + } + } + + private: + std::string query_; +}; + +} + + diff --git a/Swift/Controllers/Roster/AppearOffline.h b/Swift/Controllers/Roster/ItemOperations/AppearOffline.h index 8bd53d7..14beaa3 100644 --- a/Swift/Controllers/Roster/AppearOffline.h +++ b/Swift/Controllers/Roster/ItemOperations/AppearOffline.h @@ -7,5 +7,5 @@ #pragma once -#include <Swift/Controllers/Roster/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> #include <Swift/Controllers/Roster/ContactRosterItem.h> diff --git a/Swift/Controllers/Roster/RosterItemOperation.h b/Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h index 691c8ef..f1dff8d 100644 --- a/Swift/Controllers/Roster/RosterItemOperation.h +++ b/Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h @@ -13,8 +13,8 @@ namespace Swift { class RosterItemOperation { public: - RosterItemOperation(bool requiresLookup = false, const JID& lookupJID = JID()) : requiresLookup_(requiresLookup), lookupJID_(lookupJID) {}; - virtual ~RosterItemOperation() {}; - bool requiresLookup() const {return requiresLookup_;}; - const JID& lookupJID() const {return lookupJID_;}; + RosterItemOperation(bool requiresLookup = false, const JID& lookupJID = JID()) : requiresLookup_(requiresLookup), lookupJID_(lookupJID) {} + virtual ~RosterItemOperation() {} + bool requiresLookup() const {return requiresLookup_;} + const JID& lookupJID() const {return lookupJID_;} /** * This is called when iterating over possible subjects, so must check it's diff --git a/Swift/Controllers/Roster/ItemOperations/SetAvailableFeatures.h b/Swift/Controllers/Roster/ItemOperations/SetAvailableFeatures.h new file mode 100644 index 0000000..620a1ae --- /dev/null +++ b/Swift/Controllers/Roster/ItemOperations/SetAvailableFeatures.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * 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/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + +namespace Swift { + +class RosterItem; + +class SetAvailableFeatures : public RosterItemOperation { + public: + SetAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& availableFeatures, JID::CompareType compareType = JID::WithoutResource) : RosterItemOperation(true, jid), jid_(jid), availableFeatures_(availableFeatures), compareType_(compareType) { + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact && contact->getJID().equals(jid_, compareType_)) { + contact->setSupportedFeatures(availableFeatures_); + } + } + + private: + JID jid_; + std::set<ContactRosterItem::Feature> availableFeatures_; + JID::CompareType compareType_; +}; + +} diff --git a/Swift/Controllers/Roster/SetAvatar.h b/Swift/Controllers/Roster/ItemOperations/SetAvatar.h index 241b741..b3ec5f3 100644 --- a/Swift/Controllers/Roster/SetAvatar.h +++ b/Swift/Controllers/Roster/ItemOperations/SetAvatar.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,8 +7,11 @@ #pragma once -#include "Swiften/Elements/Presence.h" -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Roster/RosterItemOperation.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" +#include <boost/filesystem/path.hpp> + +#include <Swiften/Elements/Presence.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> namespace Swift { @@ -18,5 +21,5 @@ class RosterItem; class SetAvatar : public RosterItemOperation { public: - SetAvatar(const JID& jid, const std::string& path, JID::CompareType compareType = JID::WithoutResource) : RosterItemOperation(true, jid), jid_(jid), path_(path), compareType_(compareType) { + SetAvatar(const JID& jid, const boost::filesystem::path& path, JID::CompareType compareType = JID::WithoutResource) : RosterItemOperation(true, jid), jid_(jid), path_(path), compareType_(compareType) { } @@ -30,5 +33,5 @@ class SetAvatar : public RosterItemOperation { private: JID jid_; - std::string path_; + boost::filesystem::path path_; JID::CompareType compareType_; }; diff --git a/Swift/Controllers/Roster/ItemOperations/SetBlockingState.h b/Swift/Controllers/Roster/ItemOperations/SetBlockingState.h new file mode 100644 index 0000000..ddb2c7a --- /dev/null +++ b/Swift/Controllers/Roster/ItemOperations/SetBlockingState.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * 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/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + +namespace Swift { + +class RosterItem; + +class SetBlockingState : public RosterItemOperation { + public: + SetBlockingState(const JID& jid, ContactRosterItem::BlockState state, JID::CompareType compareType = JID::WithoutResource) : RosterItemOperation(!jid.getNode().empty(), jid), jid_(jid), state_(state), compareType_(compareType) { + if (state_ == ContactRosterItem::IsBlocked && jid.getNode().empty()) { + state_ = ContactRosterItem::IsDomainBlocked; + } + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (jid_.getNode().empty()) { + if (contact && contact->getJID().getDomain() == jid_.getDomain()) { + contact->setBlockState(state_); + } + } else { + if (contact && contact->getJID().equals(jid_, compareType_)) { + contact->setBlockState(state_); + } + } + } + + private: + JID jid_; + ContactRosterItem::BlockState state_; + JID::CompareType compareType_; +}; + +} diff --git a/Swift/Controllers/Roster/ItemOperations/SetMUC.h b/Swift/Controllers/Roster/ItemOperations/SetMUC.h new file mode 100644 index 0000000..598e5f5 --- /dev/null +++ b/Swift/Controllers/Roster/ItemOperations/SetMUC.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013-2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + +namespace Swift { + +class RosterItem; + +class SetMUC : public RosterItemOperation { + public: + SetMUC(const JID& jid, const MUCOccupant::Role& role, const MUCOccupant::Affiliation& affiliation) + : RosterItemOperation(true, jid), jid_(jid), mucRole_(role), mucAffiliation_(affiliation) { + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact && contact->getJID().equals(jid_, JID::WithResource)) { + contact->setMUCRole(mucRole_); + contact->setMUCAffiliation(mucAffiliation_); + } + } + + private: + JID jid_; + MUCOccupant::Role mucRole_; + MUCOccupant::Affiliation mucAffiliation_; +}; + +} diff --git a/Swift/Controllers/Roster/SetName.h b/Swift/Controllers/Roster/ItemOperations/SetName.h index aefb0dc..b21e4f2 100644 --- a/Swift/Controllers/Roster/SetName.h +++ b/Swift/Controllers/Roster/ItemOperations/SetName.h @@ -7,7 +7,8 @@ #pragma once -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Roster/RosterItemOperation.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> namespace Swift { diff --git a/Swift/Controllers/Roster/SetPresence.h b/Swift/Controllers/Roster/ItemOperations/SetPresence.h index 06adfa4..b298a88 100644 --- a/Swift/Controllers/Roster/SetPresence.h +++ b/Swift/Controllers/Roster/ItemOperations/SetPresence.h @@ -7,8 +7,9 @@ #pragma once -#include "Swiften/Elements/Presence.h" -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Roster/RosterItemOperation.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" +#include <Swiften/Elements/Presence.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> namespace Swift { diff --git a/Swift/Controllers/Roster/ItemOperations/SetVCard.h b/Swift/Controllers/Roster/ItemOperations/SetVCard.h new file mode 100644 index 0000000..8ee73f9 --- /dev/null +++ b/Swift/Controllers/Roster/ItemOperations/SetVCard.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/VCard.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + +namespace Swift { + +class RosterItem; + +class SetVCard : public RosterItemOperation { + public: + SetVCard(const JID& jid, VCard::ref vcard, JID::CompareType compareType = JID::WithoutResource) : RosterItemOperation(true, jid), jid_(jid), vcard_(vcard), compareType_(compareType) { + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact && contact->getJID().equals(jid_, compareType_)) { + contact->setVCard(vcard_); + } + } + + private: + JID jid_; + VCard::ref vcard_; + JID::CompareType compareType_; +}; + +} diff --git a/Swift/Controllers/Roster/LeastCommonSubsequence.h b/Swift/Controllers/Roster/LeastCommonSubsequence.h index dd3c95a..9d45679 100644 --- a/Swift/Controllers/Roster/LeastCommonSubsequence.h +++ b/Swift/Controllers/Roster/LeastCommonSubsequence.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2011 Remko Tronçon + * Copyright (c) 2011-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -8,4 +8,5 @@ #include <vector> +#include <boost/numeric/conversion/cast.hpp> namespace Swift { @@ -15,6 +16,6 @@ namespace Swift { template<typename XIt, typename YIt, typename Length, typename Predicate> void computeLeastCommonSubsequenceMatrix(XIt xBegin, XIt xEnd, YIt yBegin, YIt yEnd, std::vector<Length>& result) { - size_t width = std::distance(xBegin, xEnd) + 1; - size_t height = std::distance(yBegin, yEnd) + 1; + size_t width = static_cast<size_t>(std::distance(xBegin, xEnd) + 1); + size_t height = static_cast<size_t>(std::distance(yBegin, yEnd) + 1); result.resize(width * height); @@ -31,5 +32,5 @@ namespace Swift { for (size_t i = 1; i < width; ++i) { for (size_t j = 1; j < height; ++j) { - result[i + j*width] = (predicate(*(xBegin + i-1), *(yBegin + j-1)) ? result[(i-1) + (j-1)*width] + 1 : std::max(result[i + (j-1)*width], result[i-1 + (j*width)])); + result[i + j*width] = predicate(*(xBegin + boost::numeric_cast<long long>(i)-1), *(yBegin + boost::numeric_cast<long long >(j)-1)) ? result[(i-1) + (j-1)*width] + 1 : std::max(result[i + (j-1)*width], result[i-1 + (j*width)]); } } @@ -47,11 +48,11 @@ namespace Swift { while (xBegin < x.end() && yBegin < y.end() && insertRemovePredicate(*xBegin, *yBegin)) { if (updatePredicate(*xBegin, *yBegin)) { - updates.push_back(std::distance(x.begin(), xBegin)); - postUpdates.push_back(std::distance(y.begin(), yBegin)); + updates.push_back(static_cast<size_t>(std::distance(x.begin(), xBegin))); + postUpdates.push_back(static_cast<size_t>(std::distance(y.begin(), yBegin))); } ++xBegin; ++yBegin; } - size_t prefixLength = std::distance(x.begin(), xBegin); + size_t prefixLength = static_cast<size_t>(std::distance(x.begin(), xBegin)); // Find & handle common suffix (Optimization to reduce LCS matrix size) @@ -60,6 +61,6 @@ namespace Swift { while (xEnd.base() > xBegin && yEnd.base() > yBegin && insertRemovePredicate(*xEnd, *yEnd)) { if (updatePredicate(*xEnd, *yEnd)) { - updates.push_back(std::distance(x.begin(), xEnd.base()) - 1); - postUpdates.push_back(std::distance(y.begin(), yEnd.base()) - 1); + updates.push_back(static_cast<size_t>(std::distance(x.begin(), xEnd.base()) - 1)); + postUpdates.push_back(static_cast<size_t>(std::distance(y.begin(), yEnd.base()) - 1)); } ++xEnd; @@ -68,6 +69,6 @@ namespace Swift { // Compute lengths - size_t xLength = std::distance(xBegin, xEnd.base()); - size_t yLength = std::distance(yBegin, yEnd.base()); + size_t xLength = static_cast<size_t>(std::distance(xBegin, xEnd.base())); + size_t yLength = static_cast<size_t>(std::distance(yBegin, yEnd.base())); // Compute LCS matrix @@ -78,5 +79,5 @@ namespace Swift { size_t i = xLength; size_t j = yLength; - const size_t width = xLength + 1; + size_t width = xLength + 1; while (true) { if (i > 0 && j > 0 && insertRemovePredicate(x[prefixLength + i-1], y[prefixLength + j-1])) { diff --git a/Swift/Controllers/Roster/Roster.cpp b/Swift/Controllers/Roster/Roster.cpp index 65cf4d2..4dbc453 100644 --- a/Swift/Controllers/Roster/Roster.cpp +++ b/Swift/Controllers/Roster/Roster.cpp @@ -1,27 +1,28 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swift/Controllers/Roster/Roster.h" +#include <Swift/Controllers/Roster/Roster.h> -#include "Swiften/Base/foreach.h" #include <string> -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/RosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swift/Controllers/Roster/RosterItemOperation.h" - -#include <boost/bind.hpp> - #include <iostream> #include <set> #include <deque> +#include <boost/bind.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/RosterItem.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> + namespace Swift { -Roster::Roster(bool sortByStatus, bool fullJIDMapping) { +Roster::Roster(bool sortByStatus, bool fullJIDMapping) : blockingSupported_(false) { sortByStatus_ = sortByStatus; fullJIDMapping_ = fullJIDMapping; @@ -40,4 +41,8 @@ Roster::~Roster() { queue.insert(queue.begin(), group->getChildren().begin(), group->getChildren().end()); } + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact) { + contact->onVCardRequested.disconnect(boost::bind(boost::ref(onVCardUpdateRequested), contact->getJID())); + } delete item; } @@ -62,12 +67,13 @@ GroupRosterItem* Roster::getGroup(const std::string& groupName) { } -void Roster::setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features) { - ItemMap::const_iterator i = itemMap_.find(fullJIDMapping_ ? jid : jid.toBare()); - if (i == itemMap_.end()) { - return; +void Roster::setBlockingSupported(bool isSupported) { + if (!blockingSupported_) { + foreach(ItemMap::value_type i, itemMap_) { + foreach(ContactRosterItem* item, i.second) { + item->setBlockState(ContactRosterItem::IsUnblocked); + } } - foreach(ContactRosterItem* item, i->second) { - item->setSupportedFeatures(features); } + blockingSupported_ = isSupported; } @@ -84,8 +90,12 @@ void Roster::handleChildrenChanged(GroupRosterItem* item) { } -void Roster::addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& groupName, const std::string& avatarPath) { +void Roster::addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& groupName, const boost::filesystem::path& avatarPath) { GroupRosterItem* group(getGroup(groupName)); ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group); + item->onVCardRequested.connect(boost::bind(boost::ref(onVCardUpdateRequested), jid)); item->setAvatarPath(avatarPath); + if (blockingSupported_) { + item->setBlockState(ContactRosterItem::IsUnblocked); + } group->addChild(item); ItemMap::iterator i = itemMap_.insert(std::make_pair(fullJIDMapping_ ? jid : jid.toBare(), std::vector<ContactRosterItem*>())).first; @@ -188,4 +198,10 @@ void Roster::applyOnAllItems(const RosterItemOperation& operation) { } +void Roster::addFilter(RosterFilter* filter) { + filters_.push_back(filter); + filterAll(); + onFilterAdded(filter); +} + void Roster::removeFilter(RosterFilter* filter) { for (unsigned int i = 0; i < filters_.size(); i++) { @@ -196,8 +212,9 @@ void Roster::removeFilter(RosterFilter *filter) { } filterAll(); + onFilterRemoved(filter); } void Roster::filterContact(ContactRosterItem* contact, GroupRosterItem* group) { - int oldDisplayedSize = group->getDisplayedChildren().size(); + size_t oldDisplayedSize = group->getDisplayedChildren().size(); bool hide = true; foreach (RosterFilter *filter, filters_) { @@ -205,5 +222,5 @@ void Roster::filterContact(ContactRosterItem* contact, GroupRosterItem* group) { } group->setDisplayed(contact, filters_.empty() || !hide); - int newDisplayedSize = group->getDisplayedChildren().size(); + size_t newDisplayedSize = group->getDisplayedChildren().size(); if (oldDisplayedSize == 0 && newDisplayedSize > 0) { onGroupAdded(group); diff --git a/Swift/Controllers/Roster/Roster.h b/Swift/Controllers/Roster/Roster.h index 2fcfba5..9956cb9 100644 --- a/Swift/Controllers/Roster/Roster.h +++ b/Swift/Controllers/Roster/Roster.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -8,14 +8,16 @@ #include <string> -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Roster/RosterItemOperation.h" -#include "Swift/Controllers/Roster/RosterFilter.h" -#include <Swift/Controllers/Roster/ContactRosterItem.h> - #include <vector> #include <map> -#include "Swiften/Base/boost_bsignals.h" + #include <boost/shared_ptr.hpp> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/RosterFilter.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + namespace Swift { @@ -29,5 +31,5 @@ class Roster { ~Roster(); - void addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& group, const std::string& avatarPath); + void addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& group, const boost::filesystem::path& avatarPath); void removeContact(const JID& jid); void removeContactFromGroup(const JID& jid, const std::string& group); @@ -37,13 +39,16 @@ class Roster { void applyOnAllItems(const RosterItemOperation& operation); void applyOnItem(const RosterItemOperation& operation, const JID& jid); - void addFilter(RosterFilter *filter) {filters_.push_back(filter);filterAll();}; + void addFilter(RosterFilter* filter); void removeFilter(RosterFilter* filter); GroupRosterItem* getRoot(); - std::vector<RosterFilter*> getFilters() {return filters_;}; + std::vector<RosterFilter*> getFilters() {return filters_;} boost::signal<void (GroupRosterItem*)> onChildrenChanged; boost::signal<void (GroupRosterItem*)> onGroupAdded; boost::signal<void (RosterItem*)> onDataChanged; + boost::signal<void (JID&)> onVCardUpdateRequested; + boost::signal<void (RosterFilter* filter)> onFilterAdded; + boost::signal<void (RosterFilter* filter)> onFilterRemoved; GroupRosterItem* getGroup(const std::string& groupName); - void setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features); + void setBlockingSupported(bool isSupported); private: @@ -59,4 +64,5 @@ class Roster { bool fullJIDMapping_; bool sortByStatus_; + bool blockingSupported_; }; diff --git a/Swift/Controllers/Roster/RosterController.cpp b/Swift/Controllers/Roster/RosterController.cpp index 170bfd0..99be985 100644 --- a/Swift/Controllers/Roster/RosterController.cpp +++ b/Swift/Controllers/Roster/RosterController.cpp @@ -1,48 +1,55 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swift/Controllers/Roster/RosterController.h" +#include <Swift/Controllers/Roster/RosterController.h> #include <boost/bind.hpp> #include <boost/smart_ptr/make_shared.hpp> -#include "Swiften/JID/JID.h" -#include "Swiften/Base/foreach.h" -#include "Swift/Controllers/UIInterfaces/MainWindow.h" -#include "Swift/Controllers/UIInterfaces/MainWindowFactory.h" -#include "Swiften/Client/NickResolver.h" -#include "Swiften/Roster/GetRosterRequest.h" -#include "Swiften/Roster/SetRosterRequest.h" -#include "Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h" -#include "Swift/Controllers/XMPPEvents/ErrorEvent.h" -#include "Swiften/Presence/PresenceOracle.h" -#include "Swiften/Presence/SubscriptionManager.h" -#include "Swift/Controllers/XMPPEvents/EventController.h" -#include "Swiften/Queries/IQRouter.h" -#include "Swift/Controllers/Roster/Roster.h" -#include "Swift/Controllers/Roster/SetPresence.h" -#include "Swift/Controllers/Roster/AppearOffline.h" -#include "Swift/Controllers/Roster/SetAvatar.h" -#include "Swift/Controllers/Roster/SetName.h" -#include "Swift/Controllers/Roster/OfflineRosterFilter.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swiften/Roster/XMPPRoster.h" -#include "Swiften/Roster/XMPPRosterItem.h" -#include "Swift/Controllers/UIEvents/AddContactUIEvent.h" -#include "Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h" -#include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h" -#include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h" -#include "Swift/Controllers/UIEvents/SendFileUIEvent.h" -#include <Swiften/FileTransfer/FileTransferManager.h> -#include <Swiften/Client/NickManager.h> -#include <Swift/Controllers/Intl.h> +#include <Swiften/Base/foreach.h> #include <Swiften/Base/format.h> -#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Base/Path.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/Client/NickManager.h> +#include <Swiften/Client/NickResolver.h> #include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/FileTransfer/FileTransferManager.h> +#include <Swiften/JID/JID.h> #include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Presence/SubscriptionManager.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/Roster/GetRosterRequest.h> +#include <Swiften/Roster/SetRosterRequest.h> +#include <Swiften/Roster/XMPPRoster.h> +#include <Swiften/Roster/XMPPRosterItem.h> + +#include <Swift/Controllers/Intl.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/OfflineRosterFilter.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/RosterVCardProvider.h> +#include <Swift/Controllers/Roster/ItemOperations/AppearOffline.h> +#include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h> +#include <Swift/Controllers/Roster/ItemOperations/SetAvailableFeatures.h> +#include <Swift/Controllers/Roster/ItemOperations/SetBlockingState.h> +#include <Swift/Controllers/Roster/ItemOperations/SetName.h> +#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> +#include <Swift/Controllers/Roster/ItemOperations/SetVCard.h> #include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/UIEvents/AddContactUIEvent.h> +#include <Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/RenameGroupUIEvent.h> +#include <Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> +#include <Swift/Controllers/UIInterfaces/MainWindow.h> +#include <Swift/Controllers/UIInterfaces/MainWindowFactory.h> +#include <Swift/Controllers/XMPPEvents/ErrorEvent.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h> namespace Swift { @@ -51,9 +58,8 @@ namespace Swift { * The controller does not gain ownership of these parameters. */ -RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, FileTransferOverview* fileTransferOverview) - : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview) { +RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, FileTransferOverview* fileTransferOverview, ClientBlockListManager* clientBlockListManager, VCardManager* vcardManager) + : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), vcardManager_(vcardManager), avatarManager_(avatarManager), nickManager_(nickManager), nickResolver_(nickResolver), presenceOracle_(presenceOracle), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview), clientBlockListManager_(clientBlockListManager) { assert(fileTransferOverview); iqRouter_ = iqRouter; - presenceOracle_ = presenceOracle; subscriptionManager_ = subscriptionManager; eventController_ = eventController; @@ -61,4 +67,5 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata expandiness_ = new RosterGroupExpandinessPersister(roster_, settings); mainWindow_->setRosterModel(roster_); + rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithoutResource); changeStatusConnection_ = mainWindow_->onChangeStatusRequest.connect(boost::bind(&RosterController::handleChangeStatusRequest, this, _1, _2)); @@ -71,7 +78,9 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata presenceOracle_->onPresenceChange.connect(boost::bind(&RosterController::handleIncomingPresence, this, _1)); uiEventConnection_ = uiEventStream->onUIEvent.connect(boost::bind(&RosterController::handleUIEvent, this, _1)); - avatarManager_ = avatarManager; + + vcardManager_->onOwnVCardChanged.connect(boost::bind(&RosterController::handleOwnVCardChanged, this, _1)); avatarManager_->onAvatarChanged.connect(boost::bind(&RosterController::handleAvatarChanged, this, _1)); - mainWindow_->setMyAvatarPath(avatarManager_->getAvatarPath(myJID_).string()); + presenceOracle_->onPresenceChange.connect(boost::bind(&RosterController::handlePresenceChanged, this, _1)); + mainWindow_->setMyAvatarPath(pathToString(avatarManager_->getAvatarPath(myJID_.toBare()))); nickManager_->onOwnNickChanged.connect(boost::bind(&MainWindow::setMyNick, mainWindow_, _1)); @@ -84,4 +93,9 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata handleShowOfflineToggled(settings_->getSetting(SettingConstants::SHOW_OFFLINE)); + + ownContact_ = boost::make_shared<ContactRosterItem>(myJID_.toBare(), myJID_.toBare(), nickManager_->getOwnNick(), static_cast<GroupRosterItem*>(0)); + ownContact_->setVCard(vcardManager_->getVCard(myJID_.toBare())); + ownContact_->setAvatarPath(pathToString(avatarManager_->getAvatarPath(myJID_.toBare()))); + mainWindow_->setMyContactRosterItem(ownContact_); } @@ -98,6 +112,6 @@ RosterController::~RosterController() { delete mainWindow_; } + delete rosterVCardProvider_; delete roster_; - } @@ -125,9 +139,9 @@ void RosterController::handleOnJIDAdded(const JID& jid) { if (!groups.empty()) { foreach(const std::string& group, groups) { - roster_->addContact(jid, jid, name, group, avatarManager_->getAvatarPath(jid).string()); + roster_->addContact(jid, jid, name, group, avatarManager_->getAvatarPath(jid)); } } else { - roster_->addContact(jid, jid, name, QT_TRANSLATE_NOOP("", "Contacts"), avatarManager_->getAvatarPath(jid).string()); + roster_->addContact(jid, jid, name, QT_TRANSLATE_NOOP("", "Contacts"), avatarManager_->getAvatarPath(jid)); } applyAllPresenceTo(jid); @@ -164,5 +178,5 @@ void RosterController::handleOnJIDUpdated(const JID& jid, const std::string& old foreach(const std::string& group, groups) { if (std::find(oldGroups.begin(), oldGroups.end(), group) == oldGroups.end()) { - roster_->addContact(jid, jid, name, group, avatarManager_->getAvatarPath(jid).string()); + roster_->addContact(jid, jid, name, group, avatarManager_->getAvatarPath(jid)); } } @@ -184,4 +198,20 @@ void RosterController::handleSettingChanged(const std::string& settingPath) { } +void RosterController::handleBlockingStateChanged() { + if (clientBlockListManager_->getBlockList()->getState() == BlockList::Available) { + foreach(const JID& jid, clientBlockListManager_->getBlockList()->getItems()) { + roster_->applyOnItems(SetBlockingState(jid, ContactRosterItem::IsBlocked)); + } + } +} + +void RosterController::handleBlockingItemAdded(const JID& jid) { + roster_->applyOnItems(SetBlockingState(jid, ContactRosterItem::IsBlocked)); +} + +void RosterController::handleBlockingItemRemoved(const JID& jid) { + roster_->applyOnItems(SetBlockingState(jid, ContactRosterItem::IsUnblocked)); +} + void RosterController::handleUIEvent(boost::shared_ptr<UIEvent> event) { if (boost::shared_ptr<AddContactUIEvent> addContactEvent = boost::dynamic_pointer_cast<AddContactUIEvent>(event)) { @@ -253,8 +283,36 @@ void RosterController::updateItem(const XMPPRosterItem& item) { SetRosterRequest::ref request = SetRosterRequest::create(roster, iqRouter_); - request->onResponse.connect(boost::bind(&RosterController::handleRosterSetError, this, _1, roster)); + request->onResponse.connect(boost::bind(&RosterController::handleRosterItemUpdated, this, _1, roster)); request->send(); } +void RosterController::initBlockingCommand() { + boost::shared_ptr<BlockList> blockList = clientBlockListManager_->requestBlockList(); + + blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&RosterController::handleBlockingStateChanged, this)); + blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&RosterController::handleBlockingItemAdded, this, _1)); + blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&RosterController::handleBlockingItemRemoved, this, _1)); + roster_->setBlockingSupported(true); + if (blockList->getState() == BlockList::Available) { + foreach(const JID& jid, blockList->getItems()) { + roster_->applyOnItems(SetBlockingState(jid, ContactRosterItem::IsBlocked)); + } + } +} + +void RosterController::handleRosterItemUpdated(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload) { + if (!!error) { + handleRosterSetError(error, rosterPayload); + } + boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); + std::vector<RosterItemPayload> items = rosterPayload->getItems(); + if (blockList->getState() == BlockList::Available && items.size() > 0) { + std::vector<JID> jids = blockList->getItems(); + if (std::find(jids.begin(), jids.end(), items[0].getJID()) != jids.end()) { + roster_->applyOnItems(SetBlockingState(items[0].getJID(), ContactRosterItem::IsBlocked)); + } + } +} + void RosterController::handleRosterSetError(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload) { if (!error) { @@ -299,9 +357,23 @@ void RosterController::handleSubscriptionRequestDeclined(SubscriptionRequestEven } +void RosterController::handleOwnVCardChanged(VCard::ref vcard) { + ownContact_->setVCard(vcard); + mainWindow_->setMyContactRosterItem(ownContact_); +} + void RosterController::handleAvatarChanged(const JID& jid) { - std::string path = avatarManager_->getAvatarPath(jid).string(); + boost::filesystem::path path = avatarManager_->getAvatarPath(jid); roster_->applyOnItems(SetAvatar(jid, path)); if (jid.equals(myJID_, JID::WithoutResource)) { - mainWindow_->setMyAvatarPath(path); + mainWindow_->setMyAvatarPath(pathToString(path)); + ownContact_->setAvatarPath(pathToString(path)); + mainWindow_->setMyContactRosterItem(ownContact_); + } +} + +void RosterController::handlePresenceChanged(Presence::ref presence) { + if (presence->getFrom().equals(myJID_, JID::WithResource)) { + ownContact_->applyPresence(std::string(), presence); + mainWindow_->setMyContactRosterItem(ownContact_); } } @@ -319,8 +391,11 @@ void RosterController::handleOnCapsChanged(const JID& jid) { if (info) { std::set<ContactRosterItem::Feature> features; - if (info->hasFeature(DiscoInfo::JingleFeature) && info->hasFeature(DiscoInfo::JingleFTFeature) && info->hasFeature(DiscoInfo::JingleTransportsIBBFeature)) { + if (FileTransferManager::isSupportedBy(info)) { features.insert(ContactRosterItem::FileTransferFeature); } - roster_->setAvailableFeatures(jid, features); + if (info->hasFeature(DiscoInfo::WhiteboardFeature)) { + features.insert(ContactRosterItem::WhiteboardFeature); + } + roster_->applyOnItems(SetAvailableFeatures(jid, features)); } } diff --git a/Swift/Controllers/Roster/RosterController.h b/Swift/Controllers/Roster/RosterController.h index 5e40124..3338d7f 100644 --- a/Swift/Controllers/Roster/RosterController.h +++ b/Swift/Controllers/Roster/RosterController.h @@ -7,18 +7,21 @@ #pragma once -#include "Swiften/JID/JID.h" #include <string> #include <set> -#include "Swiften/Elements/Presence.h" -#include "Swiften/Elements/ErrorPayload.h" -#include "Swiften/Elements/RosterPayload.h" -#include "Swiften/Avatars/AvatarManager.h" -#include "Swift/Controllers/UIEvents/UIEvent.h" -#include "RosterGroupExpandinessPersister.h" -#include "Swift/Controllers/FileTransfer/FileTransferOverview.h" -#include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/Elements/RosterPayload.h> +#include <Swiften/Avatars/AvatarManager.h> +#include <Swiften/VCards/VCardManager.h> + +#include <Swift/Controllers/UIEvents/UIEvent.h> +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> +#include <Swift/Controllers/Roster/RosterGroupExpandinessPersister.h> + namespace Swift { class IQRouter; @@ -40,14 +43,19 @@ namespace Swift { class EntityCapsProvider; class FileTransferManager; + class ClientBlockListManager; + class RosterVCardProvider; class RosterController { public: - RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview); + RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview, ClientBlockListManager* clientBlockListManager, VCardManager* vcardManager); ~RosterController(); void showRosterWindow(); - MainWindow* getWindow() {return mainWindow_;}; + void setJID(const JID& jid) { myJID_ = jid; } + MainWindow* getWindow() {return mainWindow_;} boost::signal<void (StatusShow::Type, const std::string&)> onChangeStatusRequest; boost::signal<void ()> onSignOutRequest; + void handleOwnVCardChanged(VCard::ref vcard); void handleAvatarChanged(const JID& jid); + void handlePresenceChanged(Presence::ref presence); void setEnabled(bool enabled); @@ -58,4 +66,6 @@ namespace Swift { void updateItem(const XMPPRosterItem&); + void initBlockingCommand(); + private: void handleOnJIDAdded(const JID &jid); @@ -71,4 +81,5 @@ namespace Swift { void handleSubscriptionRequestDeclined(SubscriptionRequestEvent* event); void handleUIEvent(boost::shared_ptr<UIEvent> event); + void handleRosterItemUpdated(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload); void handleRosterSetError(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload); void applyAllPresenceTo(const JID& jid); @@ -77,4 +88,8 @@ namespace Swift { void handleSettingChanged(const std::string& settingPath); + void handleBlockingStateChanged(); + void handleBlockingItemAdded(const JID& jid); + void handleBlockingItemRemoved(const JID& jid); + JID myJID_; XMPPRoster* xmppRoster_; @@ -83,4 +98,5 @@ namespace Swift { Roster* roster_; OfflineRosterFilter* offlineFilter_; + VCardManager* vcardManager_; AvatarManager* avatarManager_; NickManager* nickManager_; @@ -95,5 +111,11 @@ namespace Swift { EntityCapsProvider* entityCapsManager_; FileTransferOverview* ftOverview_; + ClientBlockListManager* clientBlockListManager_; + RosterVCardProvider* rosterVCardProvider_; + boost::shared_ptr<ContactRosterItem> ownContact_; + boost::bsignals::scoped_connection blockingOnStateChangedConnection_; + boost::bsignals::scoped_connection blockingOnItemAddedConnection_; + boost::bsignals::scoped_connection blockingOnItemRemovedConnection_; boost::bsignals::scoped_connection changeStatusConnection_; boost::bsignals::scoped_connection signOutConnection_; diff --git a/Swift/Controllers/Roster/RosterVCardProvider.cpp b/Swift/Controllers/Roster/RosterVCardProvider.cpp new file mode 100644 index 0000000..954ac68 --- /dev/null +++ b/Swift/Controllers/Roster/RosterVCardProvider.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/Controllers/Roster/RosterVCardProvider.h> + +#include <Swiften/VCards/VCardManager.h> + +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/ItemOperations/SetVCard.h> + +namespace Swift { + +RosterVCardProvider::RosterVCardProvider(Roster* roster, VCardManager* vcardManager, JID::CompareType compareType) : roster_(roster), vcardManager_(vcardManager), compareType_(compareType) { + vcardUpdateRequestedConnection = roster_->onVCardUpdateRequested.connect(boost::bind(&RosterVCardProvider::handleVCardUpdateRequested, this, _1)); + vcardChangedConnection = vcardManager_->onVCardChanged.connect(boost::bind(&RosterVCardProvider::handleVCardChanged, this, _1, _2)); +} + +RosterVCardProvider::~RosterVCardProvider() { +} + +void RosterVCardProvider::handleVCardUpdateRequested(const JID& jid) { + VCard::ref vcard = vcardManager_->getVCardAndRequestWhenNeeded(jid); + if (vcard) { + handleVCardChanged(jid, vcard); + } +} + +void RosterVCardProvider::handleVCardChanged(const JID& jid, VCard::ref vcard) { + roster_->applyOnItem(SetVCard(jid, vcard, compareType_), jid); +} + + +} diff --git a/Swift/Controllers/Roster/RosterVCardProvider.h b/Swift/Controllers/Roster/RosterVCardProvider.h new file mode 100644 index 0000000..da41298 --- /dev/null +++ b/Swift/Controllers/Roster/RosterVCardProvider.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/signals/connection.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Elements/VCard.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + +class Roster; +class VCardManager; + +class RosterVCardProvider { + public: + RosterVCardProvider(Roster* roster, VCardManager* vcardManager, JID::CompareType compareType); + ~RosterVCardProvider(); + + private: + void handleVCardUpdateRequested(const JID& jid); + void handleVCardChanged(const JID& jid, VCard::ref vcard); + + private: + Roster* roster_; + VCardManager* vcardManager_; + JID::CompareType compareType_; + boost::bsignals::scoped_connection vcardUpdateRequestedConnection; + boost::bsignals::scoped_connection vcardChangedConnection; +}; + +} diff --git a/Swift/Controllers/Roster/TableRoster.cpp b/Swift/Controllers/Roster/TableRoster.cpp index c00bf4f..eb036db 100644 --- a/Swift/Controllers/Roster/TableRoster.cpp +++ b/Swift/Controllers/Roster/TableRoster.cpp @@ -10,4 +10,5 @@ #include <cassert> #include <algorithm> +#include <boost/numeric/conversion/cast.hpp> #include <Swiften/Base/foreach.h> @@ -133,11 +134,11 @@ void TableRoster::handleUpdateTimerTick() { size_t end = update.insertedRows.size(); update.insertedRows.resize(update.insertedRows.size() + itemInserts.size()); - std::transform(itemInserts.begin(), itemInserts.end(), update.insertedRows.begin() + end, CreateIndexForSection(sectionPostUpdates[i])); + std::transform(itemInserts.begin(), itemInserts.end(), update.insertedRows.begin() + boost::numeric_cast<long long>(end), CreateIndexForSection(sectionPostUpdates[i])); end = update.deletedRows.size(); update.deletedRows.resize(update.deletedRows.size() + itemRemoves.size()); - std::transform(itemRemoves.begin(), itemRemoves.end(), update.deletedRows.begin() + end, CreateIndexForSection(sectionUpdates[i])); + std::transform(itemRemoves.begin(), itemRemoves.end(), update.deletedRows.begin() + boost::numeric_cast<long long>(end), CreateIndexForSection(sectionUpdates[i])); end = update.updatedRows.size(); update.updatedRows.resize(update.updatedRows.size() + itemUpdates.size()); - std::transform(itemUpdates.begin(), itemUpdates.end(), update.updatedRows.begin() + end, CreateIndexForSection(sectionPostUpdates[i])); + std::transform(itemUpdates.begin(), itemUpdates.end(), update.updatedRows.begin() + boost::numeric_cast<long long>(end), CreateIndexForSection(sectionPostUpdates[i])); } diff --git a/Swift/Controllers/Roster/TableRoster.h b/Swift/Controllers/Roster/TableRoster.h index d4612ed..f447760 100644 --- a/Swift/Controllers/Roster/TableRoster.h +++ b/Swift/Controllers/Roster/TableRoster.h @@ -13,4 +13,5 @@ #include <Swiften/JID/JID.h> #include <Swiften/Elements/StatusShow.h> +#include <boost/filesystem/path.hpp> namespace Swift { @@ -22,5 +23,5 @@ namespace Swift { public: struct Item { - Item(const std::string& name, const std::string& description, const JID& jid, StatusShow::Type status, const std::string& avatarPath) : name(name), description(description), jid(jid), status(status), avatarPath(avatarPath) { + Item(const std::string& name, const std::string& description, const JID& jid, StatusShow::Type status, const boost::filesystem::path& avatarPath) : name(name), description(description), jid(jid), status(status), avatarPath(avatarPath) { } std::string name; @@ -28,5 +29,5 @@ namespace Swift { JID jid; StatusShow::Type status; - std::string avatarPath; + boost::filesystem::path avatarPath; }; diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp index fbee894..392a426 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp @@ -9,33 +9,38 @@ #include <cppunit/extensions/TestFactoryRegistry.h> +#include <Swiften/Avatars/NullAvatarManager.h> +#include <Swiften/Base/Algorithm.h> #include <Swiften/Base/foreach.h> -#include "Swift/Controllers/Roster/RosterController.h" -#include "Swift/Controllers/UnitTest/MockMainWindowFactory.h" -// #include "Swiften/Elements/Payload.h" -// #include "Swiften/Elements/RosterItemPayload.h" -// #include "Swiften/Elements/RosterPayload.h" -#include "Swiften/Queries/DummyIQChannel.h" -#include "Swiften/Client/DummyStanzaChannel.h" -#include "Swiften/Queries/IQRouter.h" -#include "Swiften/Roster/XMPPRosterImpl.h" -#include "Swift/Controllers/Roster/Roster.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Settings/DummySettingsProvider.h" -#include "Swiften/Avatars/NullAvatarManager.h" -#include "Swift/Controllers/XMPPEvents/EventController.h" -#include "Swiften/Presence/PresenceOracle.h" -#include "Swiften/Presence/SubscriptionManager.h" -#include "Swiften/Client/NickResolver.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h" -#include "Swiften/MUC/MUCRegistry.h" +#include <Swiften/Client/ClientBlockListManager.h> #include <Swiften/Client/DummyNickManager.h> -#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Client/DummyStanzaChannel.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/Crypto/CryptoProvider.h> +#include <Swiften/Crypto/PlatformCryptoProvider.h> #include <Swiften/Disco/CapsProvider.h> -#include <Swiften/Jingle/JingleSessionManager.h> -#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> -#include <Swiften/Base/Algorithm.h> +#include <Swiften/Disco/EntityCapsManager.h> #include <Swiften/EventLoop/DummyEventLoop.h> +#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> +#include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/MUC/MUCRegistry.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Presence/SubscriptionManager.h> +#include <Swiften/Queries/DummyIQChannel.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/Roster/XMPPRosterImpl.h> +#include <Swiften/VCards/VCardMemoryStorage.h> +// #include <Swiften/Elements/Payload.h> +// #include <Swiften/Elements/RosterItemPayload.h> +// #include <Swiften/Elements/RosterPayload.h> + +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/RosterController.h> +#include <Swift/Controllers/Settings/DummySettingsProvider.h> +#include <Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UnitTest/MockMainWindowFactory.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> using namespace Swift; @@ -83,13 +88,23 @@ class RosterControllerTest : public CppUnit::TestFixture { ftManager_ = new DummyFileTransferManager(); ftOverview_ = new FileTransferOverview(ftManager_); - rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_); + clientBlockListManager_ = new ClientBlockListManager(router_); + crypto_ = PlatformCryptoProvider::create(); + vcardStorage_ = new VCardMemoryStorage(crypto_); + vcardManager_ = new VCardManager(jid_, router_, vcardStorage_); + rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_, clientBlockListManager_, vcardManager_); mainWindow_ = mainWindowFactory_->last; - }; + } void tearDown() { delete rosterController_; + delete vcardManager_; + delete vcardStorage_; + delete crypto_; + delete clientBlockListManager_; + delete ftOverview_; delete ftManager_; delete jingleSessionManager_; - + delete entityCapsManager_; + delete capsProvider_; delete nickManager_; delete nickResolver_; @@ -106,5 +121,5 @@ class RosterControllerTest : public CppUnit::TestFixture { delete settings_; delete xmppRoster_; - }; + } GroupRosterItem* groupChild(size_t i) { @@ -134,5 +149,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(presence->getStatus(), item2->getStatusText()); - }; + } void testHighestPresence() { @@ -154,5 +169,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT(item); CPPUNIT_ASSERT_EQUAL(highPresence->getStatus(), item->getStatusText()); - }; + } void testNotHighestPresence() { @@ -174,5 +189,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT(item); CPPUNIT_ASSERT_EQUAL(highPresence->getStatus(), item->getStatusText()); - }; + } void testUnavailablePresence() { @@ -216,5 +231,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(StatusShow::None, item->getStatusShow()); CPPUNIT_ASSERT_EQUAL(lowPresenceOffline->getStatus(), item->getStatusText()); - }; + } void testAdd() { @@ -226,5 +241,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(CHILDREN.size())); //CPPUNIT_ASSERT_EQUAL(std::string("Bob"), xmppRoster_->getNameForJID(JID("foo@bar.com"))); - }; + } void testAddSubscription() { @@ -243,5 +258,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); - }; + } void testReceiveRename() { @@ -257,5 +272,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); CPPUNIT_ASSERT_EQUAL(std::string("NewName"), groupChild(0)->getChildren()[0]->getDisplayName()); - }; + } void testReceiveRegroup() { @@ -283,5 +298,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(std::string("new name"), groupChild(0)->getChildren()[0]->getDisplayName()); CPPUNIT_ASSERT_EQUAL(std::string("Best Group"), groupChild(0)->getDisplayName()); - }; + } void testSendRename() { @@ -338,4 +353,8 @@ class RosterControllerTest : public CppUnit::TestFixture { FileTransferManager* ftManager_; FileTransferOverview* ftOverview_; + ClientBlockListManager* clientBlockListManager_; + CryptoProvider* crypto_; + VCardStorage* vcardStorage_; + VCardManager* vcardManager_; }; diff --git a/Swift/Controllers/Roster/UnitTest/RosterTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterTest.cpp index 0c3e769..7d86aaf 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterTest.cpp @@ -7,9 +7,10 @@ #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> + #include <boost/shared_ptr.hpp> -#include "Swift/Controllers/Roster/Roster.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swift/Controllers/Roster/SetPresence.h" +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> using namespace Swift; diff --git a/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp b/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp index e433b50..f8e6a63 100644 --- a/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp @@ -7,4 +7,6 @@ #include <Swift/Controllers/Roster/TableRoster.h> +std::ostream& operator<<(std::ostream& os, const Swift::TableRoster::Index& i); + std::ostream& operator<<(std::ostream& os, const Swift::TableRoster::Index& i) { os << "(" << i.section << ", " << i.row << ")"; @@ -14,4 +16,5 @@ std::ostream& operator<<(std::ostream& os, const Swift::TableRoster::Index& i) { #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> + #include <boost/shared_ptr.hpp> #include <boost/variant.hpp> @@ -20,5 +23,5 @@ std::ostream& operator<<(std::ostream& os, const Swift::TableRoster::Index& i) { #include <Swift/Controllers/Roster/Roster.h> #include <Swift/Controllers/Roster/GroupRosterItem.h> -#include <Swift/Controllers/Roster/SetPresence.h> +#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> using namespace Swift; diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 70085a6..5ebbdd3 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -20,5 +20,6 @@ if env["SCONS_STAGE"] == "build" : myenv = env.Clone() myenv.BuildVersion("BuildVersion.h", project = "swift") - myenv.MergeFlags(env["BOOST_FLAGS"]) + myenv.UseFlags(env["SWIFTEN_FLAGS"]) + myenv.UseFlags(env["SWIFTEN_DEP_FLAGS"]) myenv.StaticLibrary("SwiftControllers", [ "Chat/ChatController.cpp", @@ -28,6 +29,9 @@ if env["SCONS_STAGE"] == "build" : "Chat/MUCSearchController.cpp", "Chat/UserSearchController.cpp", + "Chat/ChatMessageParser.cpp", + "ContactSuggester.cpp", "MainController.cpp", "ProfileController.cpp", + "ShowProfileController.cpp", "ContactEditController.cpp", "FileTransfer/FileTransferController.cpp", @@ -40,4 +44,5 @@ if env["SCONS_STAGE"] == "build" : "Roster/RosterItem.cpp", "Roster/Roster.cpp", + "Roster/RosterVCardProvider.cpp", "Roster/TableRoster.cpp", "EventWindowController.cpp", @@ -45,13 +50,18 @@ if env["SCONS_STAGE"] == "build" : "SystemTrayController.cpp", "XMLConsoleController.cpp", + "HistoryViewController.cpp", + "HistoryController.cpp", "FileTransferListController.cpp", + "BlockListController.cpp", "StatusTracker.cpp", "PresenceNotifier.cpp", "EventNotifier.cpp", "AdHocManager.cpp", + "AdHocController.cpp", "XMPPEvents/EventController.cpp", "UIEvents/UIEvent.cpp", "UIInterfaces/XMLConsoleWidget.cpp", "UIInterfaces/ChatListWindow.cpp", + "UIInterfaces/HighlightEditorWindow.cpp", "PreviousStatusStore.cpp", "ProfileSettingsProvider.cpp", @@ -71,5 +81,15 @@ if env["SCONS_STAGE"] == "build" : "XMPPURIController.cpp", "ChatMessageSummarizer.cpp", - "SettingConstants.cpp" + "SettingConstants.cpp", + "WhiteboardManager.cpp", + "StatusCache.cpp", + "HighlightAction.cpp", + "HighlightEditorController.cpp", + "HighlightManager.cpp", + "HighlightRule.cpp", + "Highlighter.cpp", + "ContactsFromXMPPRoster.cpp", + "ContactProvider.cpp", + "Contact.cpp" ]) @@ -83,6 +103,9 @@ if env["SCONS_STAGE"] == "build" : File("Chat/UnitTest/ChatsManagerTest.cpp"), File("Chat/UnitTest/MUCControllerTest.cpp"), + File("Chat/UnitTest/ChatMessageParserTest.cpp"), File("UnitTest/MockChatWindow.cpp"), File("UnitTest/ChatMessageSummarizerTest.cpp"), File("Settings/UnitTest/SettingsProviderHierachyTest.cpp"), + File("UnitTest/HighlightRuleTest.cpp"), + File("UnitTest/ContactSuggesterTest.cpp") ]) diff --git a/Swift/Controllers/SettingConstants.cpp b/Swift/Controllers/SettingConstants.cpp index 7ab4ac4..a5328a4 100644 --- a/Swift/Controllers/SettingConstants.cpp +++ b/Swift/Controllers/SettingConstants.cpp @@ -20,3 +20,9 @@ const SettingsProvider::Setting<bool> SettingConstants::SHOW_OFFLINE("showOfflin const SettingsProvider::Setting<std::string> SettingConstants::EXPANDED_ROSTER_GROUPS("GroupExpandiness", ""); const SettingsProvider::Setting<bool> SettingConstants::PLAY_SOUNDS("playSounds", true); +const SettingsProvider::Setting<std::string> SettingConstants::HIGHLIGHT_RULES("highlightRules", "@"); +const SettingsProvider::Setting<bool> SettingConstants::SPELL_CHECKER("spellChecker", false); +const SettingsProvider::Setting<std::string> SettingConstants::DICT_PATH("dictPath", "/usr/share/myspell/dicts/"); +const SettingsProvider::Setting<std::string> SettingConstants::PERSONAL_DICT_PATH("personaldictPath", "/home/"); +const SettingsProvider::Setting<std::string> SettingConstants::DICT_FILE("dictFile", "en_US.dic"); +const SettingsProvider::Setting<std::string> SettingConstants::INVITE_AUTO_ACCEPT_MODE("inviteAutoAcceptMode", "presence"); } diff --git a/Swift/Controllers/SettingConstants.h b/Swift/Controllers/SettingConstants.h index ff1ed72..4d25bde 100644 --- a/Swift/Controllers/SettingConstants.h +++ b/Swift/Controllers/SettingConstants.h @@ -23,4 +23,10 @@ namespace Swift { static const SettingsProvider::Setting<std::string> EXPANDED_ROSTER_GROUPS; static const SettingsProvider::Setting<bool> PLAY_SOUNDS; + static const SettingsProvider::Setting<std::string> HIGHLIGHT_RULES; + static const SettingsProvider::Setting<bool> SPELL_CHECKER; + static const SettingsProvider::Setting<std::string> DICT_PATH; + static const SettingsProvider::Setting<std::string> PERSONAL_DICT_PATH; + static const SettingsProvider::Setting<std::string> DICT_FILE; + static const SettingsProvider::Setting<std::string> INVITE_AUTO_ACCEPT_MODE; }; } diff --git a/Swift/Controllers/Settings/DummySettingsProvider.h b/Swift/Controllers/Settings/DummySettingsProvider.h index 1d6059f..0183dd3 100644 --- a/Swift/Controllers/Settings/DummySettingsProvider.h +++ b/Swift/Controllers/Settings/DummySettingsProvider.h @@ -18,23 +18,23 @@ class DummySettingsProvider : public SettingsProvider { virtual std::string getSetting(const Setting<std::string>& setting) { return stringValues.find(setting.getKey()) != stringValues.end() ? stringValues[setting.getKey()] : setting.getDefaultValue(); - }; + } virtual void storeSetting(const Setting<std::string>& setting, const std::string& value) { stringValues[setting.getKey()] = value; onSettingChanged(setting.getKey()); - }; + } virtual bool getSetting(const Setting<bool>& setting) { return boolValues.find(setting.getKey()) != boolValues.end() ? boolValues[setting.getKey()] : setting.getDefaultValue(); - }; + } virtual void storeSetting(const Setting<bool>& setting, const bool& value) { boolValues[setting.getKey()] = value; onSettingChanged(setting.getKey()); - }; + } virtual int getSetting(const Setting<int>& setting) { return intValues.find(setting.getKey()) != intValues.end() ? intValues[setting.getKey()] : setting.getDefaultValue(); - }; + } virtual void storeSetting(const Setting<int>& setting, const int& value) { intValues[setting.getKey()] = value; onSettingChanged(setting.getKey()); - }; + } virtual std::vector<std::string> getAvailableProfiles() {return std::vector<std::string>();} virtual void createProfile(const std::string& ) {} diff --git a/Swift/Controllers/Settings/UnitTest/SettingsProviderHierachyTest.cpp b/Swift/Controllers/Settings/UnitTest/SettingsProviderHierachyTest.cpp index aa4d14f..2b637a0 100644 --- a/Swift/Controllers/Settings/UnitTest/SettingsProviderHierachyTest.cpp +++ b/Swift/Controllers/Settings/UnitTest/SettingsProviderHierachyTest.cpp @@ -27,5 +27,5 @@ class SettingsProviderHierachyTest : public CppUnit::TestFixture { public: - SettingsProviderHierachyTest() : setting1("somekey", 42) {}; + SettingsProviderHierachyTest() : setting1("somekey", 42) {} void setUp() { diff --git a/Swift/Controllers/ShowProfileController.cpp b/Swift/Controllers/ShowProfileController.cpp new file mode 100644 index 0000000..d13e5a1 --- /dev/null +++ b/Swift/Controllers/ShowProfileController.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "ShowProfileController.h" + +#include <boost/bind.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/VCards/VCardManager.h> + +#include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ProfileWindowFactory.h> + +namespace Swift { + +ShowProfileController::ShowProfileController(VCardManager* vcardManager, ProfileWindowFactory* profileWindowFactory, UIEventStream* uiEventStream) : vcardManager(vcardManager), profileWindowFactory(profileWindowFactory), uiEventStream(uiEventStream) { + uiEventStream->onUIEvent.connect(boost::bind(&ShowProfileController::handleUIEvent, this, _1)); + vcardManager->onVCardChanged.connect(boost::bind(&ShowProfileController::handleVCardChanged, this, _1, _2)); +} + +ShowProfileController::~ShowProfileController() { + typedef std::pair<JID, ProfileWindow*> JIDProfileWindowPair; + foreach(const JIDProfileWindowPair& jidWndPair, openedProfileWindows) { + jidWndPair.second->onWindowAboutToBeClosed.disconnect(boost::bind(&ShowProfileController::handleProfileWindowAboutToBeClosed, this, _1)); + delete jidWndPair.second; + } + + vcardManager->onVCardChanged.disconnect(boost::bind(&ShowProfileController::handleVCardChanged, this, _1, _2)); + uiEventStream->onUIEvent.disconnect(boost::bind(&ShowProfileController::handleUIEvent, this, _1)); +} + +void ShowProfileController::handleUIEvent(UIEvent::ref event) { + ShowProfileForRosterItemUIEvent::ref showProfileEvent = boost::dynamic_pointer_cast<ShowProfileForRosterItemUIEvent>(event); + if (!showProfileEvent) { + return; + } + + if (openedProfileWindows.find(showProfileEvent->getJID()) == openedProfileWindows.end()) { + ProfileWindow* newProfileWindow = profileWindowFactory->createProfileWindow(); + newProfileWindow->setJID(showProfileEvent->getJID()); + newProfileWindow->onWindowAboutToBeClosed.connect(boost::bind(&ShowProfileController::handleProfileWindowAboutToBeClosed, this, _1)); + openedProfileWindows[showProfileEvent->getJID()] = newProfileWindow; + VCard::ref vcard = vcardManager->getVCardAndRequestWhenNeeded(showProfileEvent->getJID(), boost::posix_time::minutes(5)); + if (vcard) { + newProfileWindow->setVCard(vcard); + } else { + newProfileWindow->setProcessing(true); + } + newProfileWindow->show(); + } else { + openedProfileWindows[showProfileEvent->getJID()]->show(); + } +} + +void ShowProfileController::handleVCardChanged(const JID& jid, VCard::ref vcard) { + if (openedProfileWindows.find(jid) == openedProfileWindows.end()) { + return; + } + + ProfileWindow* profileWindow = openedProfileWindows[jid]; + profileWindow->setVCard(vcard); + profileWindow->setProcessing(false); + profileWindow->show(); +} + +void ShowProfileController::handleProfileWindowAboutToBeClosed(const JID& profileJid) { + openedProfileWindows.erase(profileJid); +} + +} diff --git a/Swift/Controllers/ShowProfileController.h b/Swift/Controllers/ShowProfileController.h new file mode 100644 index 0000000..27a0cf4 --- /dev/null +++ b/Swift/Controllers/ShowProfileController.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/VCard.h> + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + class VCardManager; + class ProfileWindow; + class ProfileWindowFactory; + class UIEventStream; + + class ShowProfileController { + public: + ShowProfileController(VCardManager* vcardManager, ProfileWindowFactory* profileWindowFactory, UIEventStream* uiEventStream); + ~ShowProfileController(); + + private: + void handleUIEvent(UIEvent::ref event); + void handleVCardChanged(const JID&, VCard::ref); + void handleProfileWindowAboutToBeClosed(const JID& profileJid); + + private: + VCardManager* vcardManager; + ProfileWindowFactory* profileWindowFactory; + UIEventStream* uiEventStream; + std::map<JID, ProfileWindow*> openedProfileWindows; + }; +} diff --git a/Swift/Controllers/SoundEventController.cpp b/Swift/Controllers/SoundEventController.cpp index d056990..a5171e2 100644 --- a/Swift/Controllers/SoundEventController.cpp +++ b/Swift/Controllers/SoundEventController.cpp @@ -13,12 +13,16 @@ #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/HighlightManager.h> namespace Swift { -SoundEventController::SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings) { +SoundEventController::SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings, HighlightManager* highlightManager) { settings_ = settings; eventController_ = eventController; soundPlayer_ = soundPlayer; eventController_->onEventQueueEventAdded.connect(boost::bind(&SoundEventController::handleEventQueueEventAdded, this, _1)); + highlightManager_ = highlightManager; + highlightManager_->onHighlight.connect(boost::bind(&SoundEventController::handleHighlight, this, _1)); + settings_->onSettingChanged.connect(boost::bind(&SoundEventController::handleSettingChanged, this, _1)); @@ -26,7 +30,14 @@ SoundEventController::SoundEventController(EventController* eventController, Sou } -void SoundEventController::handleEventQueueEventAdded(boost::shared_ptr<StanzaEvent> event) { - if (playSounds_ && !event->getConcluded()) { - soundPlayer_->playSound(SoundPlayer::MessageReceived); +void SoundEventController::handleEventQueueEventAdded(boost::shared_ptr<StanzaEvent> /*event*/) { + // message received sound is now played via highlighting + //if (playSounds_ && !event->getConcluded()) { + // soundPlayer_->playSound(SoundPlayer::MessageReceived); + //} +} + +void SoundEventController::handleHighlight(const HighlightAction& action) { + if (playSounds_ && action.playSound()) { + soundPlayer_->playSound(SoundPlayer::MessageReceived, action.getSoundFile()); } } diff --git a/Swift/Controllers/SoundEventController.h b/Swift/Controllers/SoundEventController.h index c6dec6f..c9dcab4 100644 --- a/Swift/Controllers/SoundEventController.h +++ b/Swift/Controllers/SoundEventController.h @@ -11,20 +11,24 @@ #include <Swift/Controllers/XMPPEvents/StanzaEvent.h> #include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/HighlightAction.h> namespace Swift { class EventController; class SoundPlayer; + class HighlightManager; class SoundEventController { public: - SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings); + SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings, HighlightManager* highlightManager); void setPlaySounds(bool playSounds); - bool getSoundEnabled() {return playSounds_;}; + bool getSoundEnabled() {return playSounds_;} private: void handleSettingChanged(const std::string& settingPath); void handleEventQueueEventAdded(boost::shared_ptr<StanzaEvent> event); + void handleHighlight(const HighlightAction& action); EventController* eventController_; SoundPlayer* soundPlayer_; bool playSounds_; SettingsProvider* settings_; + HighlightManager* highlightManager_; }; } diff --git a/Swift/Controllers/SoundPlayer.h b/Swift/Controllers/SoundPlayer.h index b71d759..f18a2c0 100644 --- a/Swift/Controllers/SoundPlayer.h +++ b/Swift/Controllers/SoundPlayer.h @@ -7,10 +7,12 @@ #pragma once +#include <string> + namespace Swift { class SoundPlayer { public: - virtual ~SoundPlayer() {}; + virtual ~SoundPlayer() {} enum SoundEffect{MessageReceived}; - virtual void playSound(SoundEffect sound) = 0; + virtual void playSound(SoundEffect sound, const std::string& soundResource) = 0; }; } diff --git a/Swift/Controllers/StatusCache.cpp b/Swift/Controllers/StatusCache.cpp new file mode 100644 index 0000000..3444189 --- /dev/null +++ b/Swift/Controllers/StatusCache.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2012 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/StatusCache.h> + +#include <boost/algorithm/string.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/filesystem/fstream.hpp> +#include <boost/lambda/lambda.hpp> +#include <boost/lambda/bind.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/ByteArray.h> +#include <SwifTools/Application/ApplicationPathProvider.h> + +namespace lambda = boost::lambda; + +namespace Swift { + +static const size_t MAX_ENTRIES = 200; + +StatusCache::StatusCache(ApplicationPathProvider* paths) { + paths_ = paths; + path_ = paths_->getDataDir() / "StatusCache"; + loadRecents(); +} + +StatusCache::~StatusCache() { + +} + +std::vector<StatusCache::PreviousStatus> StatusCache::getMatches(const std::string& substring, size_t maxCount) const { + std::vector<PreviousStatus> matches; + foreach (const PreviousStatus& status, previousStatuses_) { + if (substring.empty() || (boost::algorithm::ifind_first(status.first, substring) && substring != status.first)) { + matches.push_back(status); + if (matches.size() == maxCount) { + break; + } + } + } + return matches; +} + +void StatusCache::addRecent(const std::string& text, StatusShow::Type type) { + if (text.empty()) { + return; + } + previousStatuses_.remove_if(lambda::bind(&PreviousStatus::first, lambda::_1) == text && lambda::bind(&PreviousStatus::second, lambda::_1) == type); + previousStatuses_.push_front(PreviousStatus(text, type)); + for (size_t i = previousStatuses_.size(); i > MAX_ENTRIES; i--) { + previousStatuses_.pop_back(); + } + saveRecents(); +} + +void StatusCache::loadRecents() { + try { + if (boost::filesystem::exists(path_)) { + ByteArray data; + readByteArrayFromFile(data, path_); + std::string stringData = byteArrayToString(data); + std::vector<std::string> lines; + boost::split(lines, stringData, boost::is_any_of("\n")); + foreach (const std::string& line, lines) { + std::vector<std::string> bits; + boost::split(bits, line, boost::is_any_of("\t")); + if (bits.size() < 2) { + continue; + } + StatusShow::Type type; + type = static_cast<StatusShow::Type>(boost::lexical_cast<size_t>(bits[0])); + previousStatuses_.push_back(PreviousStatus(boost::trim_copy(bits[1]), type)); + } + } + } + catch (const boost::filesystem::filesystem_error& e) { + std::cerr << "ERROR: " << e.what() << std::endl; + } +} + +void StatusCache::saveRecents() { + try { + if (!boost::filesystem::exists(path_.parent_path())) { + boost::filesystem::create_directories(path_.parent_path()); + } + boost::filesystem::ofstream file(path_); + foreach (const PreviousStatus& recent, previousStatuses_) { + std::string message = recent.first; + boost::replace_all(message, "\t", " "); + file << recent.second << "\t" << message << std::endl; + } + file.close(); + } + catch (const boost::filesystem::filesystem_error& e) { + std::cerr << "ERROR: " << e.what() << std::endl; + } +} + +} + + + + diff --git a/Swift/Controllers/StatusCache.h b/Swift/Controllers/StatusCache.h new file mode 100644 index 0000000..35b3674 --- /dev/null +++ b/Swift/Controllers/StatusCache.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> +#include <utility> +#include <vector> +#include <list> +#include <iostream> + +#include <boost/filesystem/path.hpp> + +#include <Swiften/Elements/StatusShow.h> + +namespace Swift { + class ApplicationPathProvider; + class StatusCache { + public: + typedef std::pair<std::string, StatusShow::Type> PreviousStatus; + public: + StatusCache(ApplicationPathProvider* paths); + ~StatusCache(); + + std::vector<PreviousStatus> getMatches(const std::string& substring, size_t maxCount) const; + void addRecent(const std::string& text, StatusShow::Type type); + private: + void saveRecents(); + void loadRecents(); + private: + boost::filesystem::path path_; + std::list<PreviousStatus> previousStatuses_; + ApplicationPathProvider* paths_; + }; +} + + diff --git a/Swift/Controllers/StatusTracker.cpp b/Swift/Controllers/StatusTracker.cpp index 0c88f4d..6766c2e 100644 --- a/Swift/Controllers/StatusTracker.cpp +++ b/Swift/Controllers/StatusTracker.cpp @@ -9,4 +9,6 @@ #include <boost/smart_ptr/make_shared.hpp> +#include <Swiften/Elements/Idle.h> + namespace Swift { @@ -22,4 +24,5 @@ boost::shared_ptr<Presence> StatusTracker::getNextPresence() { presence->setShow(StatusShow::Away); presence->setStatus(queuedPresence_->getStatus()); + presence->addPayload(boost::make_shared<Idle>(isAutoAwaySince_)); } else { presence = queuedPresence_; @@ -36,9 +39,10 @@ void StatusTracker::setRequestedPresence(boost::shared_ptr<Presence> presence) { } -bool StatusTracker::goAutoAway() { +bool StatusTracker::goAutoAway(const int& seconds) { if (queuedPresence_->getShow() != StatusShow::Online) { return false; } isAutoAway_ = true; + isAutoAwaySince_ = boost::posix_time::second_clock::universal_time() - boost::posix_time::seconds(seconds); return true; } diff --git a/Swift/Controllers/StatusTracker.h b/Swift/Controllers/StatusTracker.h index 4f4e880..10a5c0c 100644 --- a/Swift/Controllers/StatusTracker.h +++ b/Swift/Controllers/StatusTracker.h @@ -11,4 +11,6 @@ #include "Swiften/Elements/Presence.h" +#include <boost/date_time/posix_time/posix_time_types.hpp> + namespace Swift { @@ -18,9 +20,10 @@ namespace Swift { boost::shared_ptr<Presence> getNextPresence(); void setRequestedPresence(boost::shared_ptr<Presence> presence); - bool goAutoAway(); + bool goAutoAway(const int& seconds); bool goAutoUnAway(); private: boost::shared_ptr<Presence> queuedPresence_; bool isAutoAway_; + boost::posix_time::ptime isAutoAwaySince_; }; } diff --git a/Swift/Controllers/StatusUtil.cpp b/Swift/Controllers/StatusUtil.cpp index fd1fea3..a72f340 100644 --- a/Swift/Controllers/StatusUtil.cpp +++ b/Swift/Controllers/StatusUtil.cpp @@ -7,4 +7,5 @@ #include <Swift/Controllers/StatusUtil.h> +#include <cassert> #include <Swift/Controllers/Intl.h> @@ -20,4 +21,5 @@ std::string statusShowTypeToFriendlyName(StatusShow::Type type) { case StatusShow::None: return QT_TRANSLATE_NOOP("", "Offline"); } + assert(false); return ""; } diff --git a/Swift/Controllers/Storages/AvatarFileStorage.cpp b/Swift/Controllers/Storages/AvatarFileStorage.cpp index b39e586..671e0cb 100644 --- a/Swift/Controllers/Storages/AvatarFileStorage.cpp +++ b/Swift/Controllers/Storages/AvatarFileStorage.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -13,10 +13,10 @@ #include <Swiften/Base/foreach.h> #include <Swiften/Base/String.h> -#include <Swiften/StringCodecs/SHA1.h> #include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/Crypto/CryptoProvider.h> namespace Swift { -AvatarFileStorage::AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile) : avatarsDir(avatarsDir), avatarsFile(avatarsFile) { +AvatarFileStorage::AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile, CryptoProvider* crypto) : avatarsDir(avatarsDir), avatarsFile(avatarsFile), crypto(crypto) { if (boost::filesystem::exists(avatarsFile)) { try { @@ -48,5 +48,5 @@ bool AvatarFileStorage::hasAvatar(const std::string& hash) const { void AvatarFileStorage::addAvatar(const std::string& hash, const ByteArray& avatar) { - assert(Hexify::hexify(SHA1::getHash(avatar)) == hash); + assert(Hexify::hexify(crypto->getSHA1Hash(avatar)) == hash); boost::filesystem::path avatarPath = getAvatarPath(hash); @@ -70,5 +70,5 @@ boost::filesystem::path AvatarFileStorage::getAvatarPath(const std::string& hash ByteArray AvatarFileStorage::getAvatar(const std::string& hash) const { ByteArray data; - readByteArrayFromFile(data, getAvatarPath(hash).string()); + readByteArrayFromFile(data, getAvatarPath(hash)); return data; } diff --git a/Swift/Controllers/Storages/AvatarFileStorage.h b/Swift/Controllers/Storages/AvatarFileStorage.h index b7e73f5..85a6463 100644 --- a/Swift/Controllers/Storages/AvatarFileStorage.h +++ b/Swift/Controllers/Storages/AvatarFileStorage.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -16,7 +16,9 @@ namespace Swift { + class CryptoProvider; + class AvatarFileStorage : public AvatarStorage { public: - AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile); + AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile, CryptoProvider* crypto); virtual bool hasAvatar(const std::string& hash) const; @@ -35,4 +37,5 @@ namespace Swift { boost::filesystem::path avatarsDir; boost::filesystem::path avatarsFile; + CryptoProvider* crypto; typedef std::map<JID, std::string> JIDAvatarMap; JIDAvatarMap jidAvatars; diff --git a/Swift/Controllers/Storages/CertificateFileStorage.cpp b/Swift/Controllers/Storages/CertificateFileStorage.cpp index a4a95c7..2c242cd 100644 --- a/Swift/Controllers/Storages/CertificateFileStorage.cpp +++ b/Swift/Controllers/Storages/CertificateFileStorage.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -9,13 +9,14 @@ #include <iostream> #include <boost/filesystem/fstream.hpp> +#include <boost/numeric/conversion/cast.hpp> -#include <Swiften/StringCodecs/SHA1.h> #include <Swiften/StringCodecs/Hexify.h> #include <Swiften/TLS/CertificateFactory.h> #include <Swiften/Base/Log.h> +#include <Swiften/Crypto/CryptoProvider.h> namespace Swift { -CertificateFileStorage::CertificateFileStorage(const boost::filesystem::path& path, CertificateFactory* certificateFactory) : path(path), certificateFactory(certificateFactory) { +CertificateFileStorage::CertificateFileStorage(const boost::filesystem::path& path, CertificateFactory* certificateFactory, CryptoProvider* crypto) : path(path), certificateFactory(certificateFactory), crypto(crypto) { } @@ -24,6 +25,6 @@ bool CertificateFileStorage::hasCertificate(Certificate::ref certificate) const if (boost::filesystem::exists(certificatePath)) { ByteArray data; - readByteArrayFromFile(data, certificatePath.string()); - Certificate::ref storedCertificate = certificateFactory->createCertificateFromDER(data); + readByteArrayFromFile(data, certificatePath); + Certificate::ref storedCertificate(certificateFactory->createCertificateFromDER(data)); if (storedCertificate && storedCertificate->toDER() == certificate->toDER()) { return true; @@ -51,10 +52,10 @@ void CertificateFileStorage::addCertificate(Certificate::ref certificate) { boost::filesystem::ofstream file(certificatePath, boost::filesystem::ofstream::binary|boost::filesystem::ofstream::out); ByteArray data = certificate->toDER(); - file.write(reinterpret_cast<const char*>(vecptr(data)), data.size()); + file.write(reinterpret_cast<const char*>(vecptr(data)), boost::numeric_cast<std::streamsize>(data.size())); file.close(); } boost::filesystem::path CertificateFileStorage::getCertificatePath(Certificate::ref certificate) const { - return path / Hexify::hexify(SHA1::getHash(certificate->toDER())); + return path / Hexify::hexify(crypto->getSHA1Hash(certificate->toDER())); } diff --git a/Swift/Controllers/Storages/CertificateFileStorage.h b/Swift/Controllers/Storages/CertificateFileStorage.h index f7a60b9..12151d0 100644 --- a/Swift/Controllers/Storages/CertificateFileStorage.h +++ b/Swift/Controllers/Storages/CertificateFileStorage.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -13,8 +13,9 @@ namespace Swift { class CertificateFactory; + class CryptoProvider; class CertificateFileStorage : public CertificateStorage { public: - CertificateFileStorage(const boost::filesystem::path& path, CertificateFactory* certificateFactory); + CertificateFileStorage(const boost::filesystem::path& path, CertificateFactory* certificateFactory, CryptoProvider* crypto); virtual bool hasCertificate(Certificate::ref certificate) const; @@ -27,4 +28,5 @@ namespace Swift { boost::filesystem::path path; CertificateFactory* certificateFactory; + CryptoProvider* crypto; }; diff --git a/Swift/Controllers/Storages/CertificateFileStorageFactory.h b/Swift/Controllers/Storages/CertificateFileStorageFactory.h index b215165..6834619 100644 --- a/Swift/Controllers/Storages/CertificateFileStorageFactory.h +++ b/Swift/Controllers/Storages/CertificateFileStorageFactory.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -12,12 +12,13 @@ namespace Swift { class CertificateFactory; + class CryptoProvider; class CertificateFileStorageFactory : public CertificateStorageFactory { public: - CertificateFileStorageFactory(const boost::filesystem::path& basePath, CertificateFactory* certificateFactory) : basePath(basePath), certificateFactory(certificateFactory) {} + CertificateFileStorageFactory(const boost::filesystem::path& basePath, CertificateFactory* certificateFactory, CryptoProvider* crypto) : basePath(basePath), certificateFactory(certificateFactory), crypto(crypto) {} virtual CertificateStorage* createCertificateStorage(const JID& profile) const { boost::filesystem::path profilePath = basePath / profile.toString(); - return new CertificateFileStorage(profilePath / "certificates", certificateFactory); + return new CertificateFileStorage(profilePath / "certificates", certificateFactory, crypto); } @@ -25,4 +26,5 @@ namespace Swift { boost::filesystem::path basePath; CertificateFactory* certificateFactory; + CryptoProvider* crypto; }; } diff --git a/Swift/Controllers/Storages/CertificateStorageTrustChecker.h b/Swift/Controllers/Storages/CertificateStorageTrustChecker.h index 40838dd..df15575 100644 --- a/Swift/Controllers/Storages/CertificateStorageTrustChecker.h +++ b/Swift/Controllers/Storages/CertificateStorageTrustChecker.h @@ -19,16 +19,16 @@ namespace Swift { } - virtual bool isCertificateTrusted(Certificate::ref certificate) { - lastCertificate = certificate; - return storage->hasCertificate(certificate); + virtual bool isCertificateTrusted(const std::vector<Certificate::ref>& certificateChain) { + lastCertificateChain = std::vector<Certificate::ref>(certificateChain.begin(), certificateChain.end()); + return certificateChain.empty() ? false : storage->hasCertificate(certificateChain[0]); } - Certificate::ref getLastCertificate() const { - return lastCertificate; + const std::vector<Certificate::ref>& getLastCertificateChain() const { + return lastCertificateChain; } private: CertificateStorage* storage; - Certificate::ref lastCertificate; + std::vector<Certificate::ref> lastCertificateChain; }; } diff --git a/Swift/Controllers/Storages/FileStorages.cpp b/Swift/Controllers/Storages/FileStorages.cpp index 6447099..e1b681f 100644 --- a/Swift/Controllers/Storages/FileStorages.cpp +++ b/Swift/Controllers/Storages/FileStorages.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -10,13 +10,20 @@ #include "Swift/Controllers/Storages/CapsFileStorage.h" #include "Swift/Controllers/Storages/RosterFileStorage.h" +#include <Swiften/History/SQLiteHistoryStorage.h> +#include <Swiften/Base/Path.h> namespace Swift { -FileStorages::FileStorages(const boost::filesystem::path& baseDir, const JID& jid) { - std::string profile = jid.toBare(); - vcardStorage = new VCardFileStorage(baseDir / profile / "vcards"); +FileStorages::FileStorages(const boost::filesystem::path& baseDir, const JID& jid, CryptoProvider* crypto) { + boost::filesystem::path profile = stringToPath(jid.toBare()); + vcardStorage = new VCardFileStorage(baseDir / profile / "vcards", crypto); capsStorage = new CapsFileStorage(baseDir / "caps"); - avatarStorage = new AvatarFileStorage(baseDir / "avatars", baseDir / profile / "avatars"); + avatarStorage = new AvatarFileStorage(baseDir / "avatars", baseDir / profile / "avatars", crypto); rosterStorage = new RosterFileStorage(baseDir / profile / "roster.xml"); +#ifdef SWIFT_EXPERIMENTAL_HISTORY + historyStorage = new SQLiteHistoryStorage(baseDir / "history.db"); +#else + historyStorage = NULL; +#endif } @@ -26,4 +33,5 @@ FileStorages::~FileStorages() { delete capsStorage; delete vcardStorage; + delete historyStorage; } @@ -44,3 +52,11 @@ RosterStorage* FileStorages::getRosterStorage() const { } +HistoryStorage* FileStorages::getHistoryStorage() const { +#ifdef SWIFT_EXPERIMENTAL_HISTORY + return historyStorage; +#else + return NULL; +#endif +} + } diff --git a/Swift/Controllers/Storages/FileStorages.h b/Swift/Controllers/Storages/FileStorages.h index 28df314..1e914b9 100644 --- a/Swift/Controllers/Storages/FileStorages.h +++ b/Swift/Controllers/Storages/FileStorages.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -9,5 +9,5 @@ #include <boost/filesystem/path.hpp> -#include "Swiften/Client/Storages.h" +#include <Swiften/Client/Storages.h> namespace Swift { @@ -16,5 +16,7 @@ namespace Swift { class CapsFileStorage; class RosterFileStorage; + class HistoryStorage; class JID; + class CryptoProvider; /** @@ -37,5 +39,5 @@ namespace Swift { * The bare JID will be used as the subdir name. */ - FileStorages(const boost::filesystem::path& baseDir, const JID& jid); + FileStorages(const boost::filesystem::path& baseDir, const JID& jid, CryptoProvider*); ~FileStorages(); @@ -44,4 +46,5 @@ namespace Swift { virtual CapsStorage* getCapsStorage() const; virtual RosterStorage* getRosterStorage() const; + virtual HistoryStorage* getHistoryStorage() const; private: @@ -50,4 +53,5 @@ namespace Swift { CapsFileStorage* capsStorage; RosterFileStorage* rosterStorage; + HistoryStorage* historyStorage; }; } diff --git a/Swift/Controllers/Storages/FileStoragesFactory.h b/Swift/Controllers/Storages/FileStoragesFactory.h index 0676bc3..c119dcf 100644 --- a/Swift/Controllers/Storages/FileStoragesFactory.h +++ b/Swift/Controllers/Storages/FileStoragesFactory.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -11,14 +11,17 @@ namespace Swift { + class CryptoProvider; + class FileStoragesFactory : public StoragesFactory { public: - FileStoragesFactory(const boost::filesystem::path& basePath) : basePath(basePath) {} + FileStoragesFactory(const boost::filesystem::path& basePath, CryptoProvider* crypto) : basePath(basePath), crypto(crypto) {} virtual Storages* createStorages(const JID& profile) const { - return new FileStorages(basePath, profile); + return new FileStorages(basePath, profile, crypto); } private: boost::filesystem::path basePath; + CryptoProvider* crypto; }; } diff --git a/Swift/Controllers/Storages/MemoryStoragesFactory.h b/Swift/Controllers/Storages/MemoryStoragesFactory.h index 0dea349..e18f9fc 100644 --- a/Swift/Controllers/Storages/MemoryStoragesFactory.h +++ b/Swift/Controllers/Storages/MemoryStoragesFactory.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -12,12 +12,15 @@ namespace Swift { class JID; + class CryptoProvider; class MemoryStoragesFactory : public StoragesFactory { public: - MemoryStoragesFactory() {} + MemoryStoragesFactory(CryptoProvider* cryptoProvider) : cryptoProvider_(cryptoProvider) {} virtual Storages* createStorages(const JID& profile) const { - return new MemoryStorages(); + return new MemoryStorages(cryptoProvider_); } + private: + CryptoProvider* cryptoProvider_; }; } diff --git a/Swift/Controllers/Storages/VCardFileStorage.cpp b/Swift/Controllers/Storages/VCardFileStorage.cpp index d799a90..876d642 100644 --- a/Swift/Controllers/Storages/VCardFileStorage.cpp +++ b/Swift/Controllers/Storages/VCardFileStorage.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -14,6 +14,7 @@ #include <Swiften/Base/String.h> #include <Swiften/StringCodecs/Hexify.h> -#include <Swiften/StringCodecs/SHA1.h> #include <Swiften/Base/foreach.h> +#include <Swiften/Base/Path.h> +#include <Swiften/Crypto/CryptoProvider.h> #include "Swiften/JID/JID.h" #include "Swiften/Elements/VCard.h" @@ -26,5 +27,5 @@ using namespace Swift; typedef GenericPayloadPersister<VCard, VCardParser, VCardSerializer> VCardPersister; -VCardFileStorage::VCardFileStorage(boost::filesystem::path dir) : vcardsPath(dir) { +VCardFileStorage::VCardFileStorage(boost::filesystem::path dir, CryptoProvider* crypto) : VCardStorage(crypto), vcardsPath(dir), crypto(crypto) { cacheFile = vcardsPath / "phashes"; if (boost::filesystem::exists(cacheFile)) { @@ -58,5 +59,15 @@ boost::shared_ptr<VCard> VCardFileStorage::getVCard(const JID& jid) const { } +boost::posix_time::ptime VCardFileStorage::getVCardWriteTime(const JID& jid) const { + if (vcardWriteTimes.find(jid) == vcardWriteTimes.end()) { + return boost::posix_time::ptime(); + } + else { + return vcardWriteTimes.at(jid); + } +} + void VCardFileStorage::setVCard(const JID& jid, VCard::ref v) { + vcardWriteTimes[jid] = boost::posix_time::second_clock::universal_time(); VCardPersister().savePayload(v, getVCardPath(jid)); getAndUpdatePhotoHash(jid, v); @@ -67,5 +78,5 @@ boost::filesystem::path VCardFileStorage::getVCardPath(const JID& jid) const { std::string file(jid.toString()); String::replaceAll(file, '/', "%2f"); - return boost::filesystem::path(vcardsPath / (file + ".xml")); + return boost::filesystem::path(vcardsPath / stringToPath(file + ".xml")); } catch (const boost::filesystem::filesystem_error& e) { @@ -89,5 +100,5 @@ std::string VCardFileStorage::getAndUpdatePhotoHash(const JID& jid, VCard::ref v std::string hash; if (vCard && !vCard->getPhoto().empty()) { - hash = Hexify::hexify(SHA1::getHash(vCard->getPhoto())); + hash = Hexify::hexify(crypto->getSHA1Hash(vCard->getPhoto())); } std::pair<PhotoHashMap::iterator, bool> r = photoHashes.insert(std::make_pair(jid, hash)); diff --git a/Swift/Controllers/Storages/VCardFileStorage.h b/Swift/Controllers/Storages/VCardFileStorage.h index ba422f4..8ba001e 100644 --- a/Swift/Controllers/Storages/VCardFileStorage.h +++ b/Swift/Controllers/Storages/VCardFileStorage.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -15,9 +15,12 @@ namespace Swift { + class CryptoProvider; + class VCardFileStorage : public VCardStorage { public: - VCardFileStorage(boost::filesystem::path dir); + VCardFileStorage(boost::filesystem::path dir, CryptoProvider* crypto); virtual VCard::ref getVCard(const JID& jid) const; + virtual boost::posix_time::ptime getVCardWriteTime(const JID& jid) const; virtual void setVCard(const JID& jid, VCard::ref v); @@ -32,7 +35,9 @@ namespace Swift { private: boost::filesystem::path vcardsPath; + CryptoProvider* crypto; boost::filesystem::path cacheFile; typedef std::map<JID, std::string> PhotoHashMap; mutable PhotoHashMap photoHashes; + std::map<JID, boost::posix_time::ptime> vcardWriteTimes; }; } diff --git a/Swift/Controllers/SystemTray.h b/Swift/Controllers/SystemTray.h index 736b1fa..b71a783 100644 --- a/Swift/Controllers/SystemTray.h +++ b/Swift/Controllers/SystemTray.h @@ -12,5 +12,5 @@ namespace Swift { class SystemTray { public: - virtual ~SystemTray(){}; + virtual ~SystemTray(){} virtual void setUnreadMessages(bool some) = 0; virtual void setStatusType(StatusShow::Type type) = 0; diff --git a/Swift/Controllers/Translator.cpp b/Swift/Controllers/Translator.cpp index 82fc46e..b7766ca 100644 --- a/Swift/Controllers/Translator.cpp +++ b/Swift/Controllers/Translator.cpp @@ -11,5 +11,5 @@ namespace Swift { -struct DefaultTranslator : public Translator { +static struct DefaultTranslator : public Translator { virtual std::string translate(const std::string& text, const std::string&) { return text; 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/AddContactUIEvent.h b/Swift/Controllers/UIEvents/AddContactUIEvent.h index d92d3af..6b70b76 100644 --- a/Swift/Controllers/UIEvents/AddContactUIEvent.h +++ b/Swift/Controllers/UIEvents/AddContactUIEvent.h @@ -15,13 +15,13 @@ namespace Swift { class AddContactUIEvent : public UIEvent { public: - AddContactUIEvent(const JID& jid, const std::string& name, const std::set<std::string>& groups) : jid_(jid), name_(name), groups_(groups) {}; + AddContactUIEvent(const JID& jid, const std::string& name, const std::set<std::string>& groups) : jid_(jid), name_(name), groups_(groups) {} const std::string& getName() const { return name_; - }; + } const JID& getJID() const { return jid_; - }; + } const std::set<std::string>& getGroups() const { diff --git a/Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h b/Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h index 210da3e..df01d6c 100644 --- a/Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h +++ b/Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h @@ -15,5 +15,5 @@ namespace Swift { class AddMUCBookmarkUIEvent : public UIEvent { public: - AddMUCBookmarkUIEvent(const MUCBookmark& bookmark) : bookmark(bookmark) {}; + AddMUCBookmarkUIEvent(const MUCBookmark& bookmark) : bookmark(bookmark) {} const MUCBookmark& getBookmark() { return bookmark; } 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/CreateImpromptuMUCUIEvent.h b/Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h new file mode 100644 index 0000000..57e181d --- /dev/null +++ b/Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + +class CreateImpromptuMUCUIEvent : public UIEvent { + public: + CreateImpromptuMUCUIEvent(const std::vector<JID>& jids, const JID& roomJID = JID(), const std::string reason = "") : jids_(jids), roomJID_(roomJID), reason_(reason) { } + + std::vector<JID> getJIDs() const { return jids_; } + JID getRoomJID() const { return roomJID_; } + std::string getReason() const { return reason_; } + private: + std::vector<JID> jids_; + JID roomJID_; + std::string reason_; +}; + +} diff --git a/Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h b/Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h index 2b10f09..7723d89 100644 --- a/Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h +++ b/Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h @@ -15,8 +15,8 @@ namespace Swift { class EditMUCBookmarkUIEvent : public UIEvent { public: - EditMUCBookmarkUIEvent(const MUCBookmark& oldBookmark, const MUCBookmark& newBookmark) : oldBookmark(oldBookmark) , newBookmark(newBookmark) {}; + EditMUCBookmarkUIEvent(const MUCBookmark& oldBookmark, const MUCBookmark& newBookmark) : oldBookmark(oldBookmark) , newBookmark(newBookmark) {} - const MUCBookmark& getOldBookmark() {return oldBookmark;}; - const MUCBookmark& getNewBookmark() {return newBookmark;}; + const MUCBookmark& getOldBookmark() {return oldBookmark;} + const MUCBookmark& getNewBookmark() {return newBookmark;} private: diff --git a/Swift/Controllers/UIEvents/InviteToMUCUIEvent.h b/Swift/Controllers/UIEvents/InviteToMUCUIEvent.h new file mode 100644 index 0000000..cb9d20b --- /dev/null +++ b/Swift/Controllers/UIEvents/InviteToMUCUIEvent.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <vector> + +#include <Swift/Controllers/UIEvents/UIEvent.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + class InviteToMUCUIEvent : public UIEvent { + public: + typedef boost::shared_ptr<InviteToMUCUIEvent> ref; + + InviteToMUCUIEvent(const JID& room, const std::vector<JID>& JIDsToInvite, const std::string& reason) : room_(room), invite_(JIDsToInvite), reason_(reason) { + } + + const JID& getRoom() const { + return room_; + } + + const std::vector<JID> getInvites() const { + return invite_; + } + + const std::string getReason() const { + return reason_; + } + + private: + JID room_; + std::vector<JID> invite_; + std::string reason_; + }; +} diff --git a/Swift/Controllers/UIEvents/JoinMUCUIEvent.h b/Swift/Controllers/UIEvents/JoinMUCUIEvent.h index b3ff8c7..e046942 100644 --- a/Swift/Controllers/UIEvents/JoinMUCUIEvent.h +++ b/Swift/Controllers/UIEvents/JoinMUCUIEvent.h @@ -19,5 +19,5 @@ namespace Swift { public: typedef boost::shared_ptr<JoinMUCUIEvent> ref; - JoinMUCUIEvent(const JID& jid, const boost::optional<std::string>& password = boost::optional<std::string>(), const boost::optional<std::string>& nick = boost::optional<std::string>(), bool joinAutomaticallyInFuture = false, bool createAsReservedRoomIfNew = false) : jid_(jid), nick_(nick), joinAutomatically_(joinAutomaticallyInFuture), createAsReservedRoomIfNew_(createAsReservedRoomIfNew), password_(password) {}; + JoinMUCUIEvent(const JID& jid, const boost::optional<std::string>& password = boost::optional<std::string>(), const boost::optional<std::string>& nick = boost::optional<std::string>(), bool joinAutomaticallyInFuture = false, bool createAsReservedRoomIfNew = false, bool isImpromptu = false, bool isContinuation = false) : jid_(jid), nick_(nick), joinAutomatically_(joinAutomaticallyInFuture), createAsReservedRoomIfNew_(createAsReservedRoomIfNew), password_(password), isImpromptuMUC_(isImpromptu), isContinuation_(isContinuation) {} const boost::optional<std::string>& getNick() const {return nick_;} const JID& getJID() const {return jid_;} @@ -25,4 +25,7 @@ namespace Swift { bool getCreateAsReservedRoomIfNew() const {return createAsReservedRoomIfNew_;} const boost::optional<std::string>& getPassword() const {return password_;} + bool isImpromptu() const {return isImpromptuMUC_;} + bool isContinuation() const {return isContinuation_;} + private: JID jid_; @@ -31,4 +34,6 @@ namespace Swift { bool createAsReservedRoomIfNew_; boost::optional<std::string> password_; + bool isImpromptuMUC_; + bool isContinuation_; }; } diff --git a/Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h b/Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h index ea2e609..0df40f9 100644 --- a/Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h +++ b/Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h @@ -15,5 +15,5 @@ namespace Swift { class RemoveMUCBookmarkUIEvent : public UIEvent { public: - RemoveMUCBookmarkUIEvent(const MUCBookmark& bookmark) : bookmark(bookmark) {}; + RemoveMUCBookmarkUIEvent(const MUCBookmark& bookmark) : bookmark(bookmark) {} const MUCBookmark& getBookmark() { return bookmark; } diff --git a/Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h b/Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h index 7e5236a..617daf3 100644 --- a/Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h +++ b/Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h @@ -14,7 +14,7 @@ namespace Swift { class RemoveRosterItemUIEvent : public UIEvent { public: - RemoveRosterItemUIEvent(const JID& jid) : jid_(jid) {}; - virtual ~RemoveRosterItemUIEvent() {}; - JID getJID() {return jid_;}; + RemoveRosterItemUIEvent(const JID& jid) : jid_(jid) {} + virtual ~RemoveRosterItemUIEvent() {} + JID getJID() {return jid_;} private: JID jid_; diff --git a/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h b/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h index c3b4b49..a0b51f2 100644 --- a/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h +++ b/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h @@ -14,5 +14,5 @@ namespace Swift { class RequestAdHocUIEvent : public UIEvent { public: - RequestAdHocUIEvent(const DiscoItems::Item& command) : command_(command) {}; + RequestAdHocUIEvent(const DiscoItems::Item& command) : command_(command) {} const DiscoItems::Item& getCommand() const {return command_;} private: diff --git a/Swift/Controllers/UIEvents/RequestAdHocWithJIDUIEvent.h b/Swift/Controllers/UIEvents/RequestAdHocWithJIDUIEvent.h new file mode 100644 index 0000000..2b1fcea --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestAdHocWithJIDUIEvent.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swift/Controllers/UIEvents/UIEvent.h" + +namespace Swift { + class RequestAdHocWithJIDUIEvent : public UIEvent { + public: + RequestAdHocWithJIDUIEvent(const JID& jid, const std::string& node) : jid_(jid), node_(node) {} + JID getJID() const { return jid_; } + std::string getNode() const { return node_; } + private: + JID jid_; + std::string node_; + }; +} diff --git a/Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h b/Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h index 5a071cf..7fe1926 100644 --- a/Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h +++ b/Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h @@ -16,9 +16,9 @@ namespace Swift { public: - RequestAddUserDialogUIEvent(const JID& predefinedJID, const std::string& predefinedName) : preJID_(predefinedJID), preName_(predefinedName) {}; - RequestAddUserDialogUIEvent() : preJID_(), preName_() {}; + RequestAddUserDialogUIEvent(const JID& predefinedJID, const std::string& predefinedName) : preJID_(predefinedJID), preName_(predefinedName) {} + RequestAddUserDialogUIEvent() : preJID_(), preName_() {} - const JID& getPredefinedJID() const { return preJID_; }; - const std::string& getPredefinedName() const { return preName_; }; + const JID& getPredefinedJID() const { return preJID_; } + const std::string& getPredefinedName() const { return preName_; } private: diff --git a/Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h b/Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h new file mode 100644 index 0000000..d29cb4f --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + +class RequestBlockListDialogUIEvent : public UIEvent { +}; + +} diff --git a/Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h b/Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h new file mode 100644 index 0000000..9b7abcb --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +#include <Swiften/JID/JID.h> + +namespace Swift { + +class RequestChangeBlockStateUIEvent : public UIEvent { + public: + enum BlockState { + Blocked, + Unblocked + }; + + public: + RequestChangeBlockStateUIEvent(BlockState newState, const JID& contact) : state_(newState), contact_(contact) {} + + BlockState getBlockState() const { + return state_; + } + + JID getContact() const { + return contact_; + } + private: + BlockState state_; + JID contact_; +}; + +} diff --git a/Swift/Controllers/UIEvents/RequestChatUIEvent.h b/Swift/Controllers/UIEvents/RequestChatUIEvent.h index b1e86ed..4ef954f 100644 --- a/Swift/Controllers/UIEvents/RequestChatUIEvent.h +++ b/Swift/Controllers/UIEvents/RequestChatUIEvent.h @@ -14,5 +14,5 @@ namespace Swift { class RequestChatUIEvent : public UIEvent { public: - RequestChatUIEvent(const JID& contact) : contact_(contact) {}; + RequestChatUIEvent(const JID& contact) : contact_(contact) {} JID getContact() {return contact_;} private: diff --git a/Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h b/Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h new file mode 100644 index 0000000..42e22a2 --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + + class RequestHighlightEditorUIEvent : public UIEvent { + }; + +} diff --git a/Swift/Controllers/UIEvents/RequestHistoryUIEvent.h b/Swift/Controllers/UIEvents/RequestHistoryUIEvent.h new file mode 100644 index 0000000..025e91f --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestHistoryUIEvent.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + class RequestHistoryUIEvent : public UIEvent { + }; +} diff --git a/Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h b/Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h new file mode 100644 index 0000000..58f45d1 --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <vector> + +#include <Swift/Controllers/UIEvents/UIEvent.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + class RequestInviteToMUCUIEvent : public UIEvent { + public: + typedef boost::shared_ptr<RequestInviteToMUCUIEvent> ref; + + enum ImpromptuMode { + Impromptu, + NotImpromptu + }; + + RequestInviteToMUCUIEvent(const JID& room, const std::vector<JID>& JIDsToInvite, ImpromptuMode impromptu) : room_(room), invite_(JIDsToInvite) { + isImpromptu_ = impromptu == Impromptu; + } + + const JID& getRoom() const { + return room_; + } + + const std::vector<JID> getInvites() const { + return invite_; + } + + bool isImpromptu() const { + return isImpromptu_; + } + + private: + JID room_; + std::vector<JID> invite_; + bool isImpromptu_; + }; +} diff --git a/Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h b/Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h new file mode 100644 index 0000000..5c44da7 --- /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/ShowProfileForRosterItemUIEvent.h b/Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h new file mode 100644 index 0000000..4a603ea --- /dev/null +++ b/Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * 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 ShowProfileForRosterItemUIEvent : public UIEvent { + public: + typedef boost::shared_ptr<ShowProfileForRosterItemUIEvent> ref; + public: + ShowProfileForRosterItemUIEvent(const JID& jid) : jid_(jid) {} + virtual ~ShowProfileForRosterItemUIEvent() {} + JID getJID() const {return jid_;} + private: + JID jid_; +}; + +} diff --git a/Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h b/Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h new file mode 100644 index 0000000..bb72d9b --- /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/UIEvents/UIEventStream.h b/Swift/Controllers/UIEvents/UIEventStream.h index e174029..b1337a2 100644 --- a/Swift/Controllers/UIEvents/UIEventStream.h +++ b/Swift/Controllers/UIEvents/UIEventStream.h @@ -19,5 +19,5 @@ namespace Swift { void send(boost::shared_ptr<UIEvent> event) { onUIEvent(event); - }; + } }; } diff --git a/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h index f7a5d39..07319c2 100644 --- a/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h +++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,8 +7,12 @@ #pragma once +#include <Swiften/Base/boost_bsignals.h> + namespace Swift { class AdHocCommandWindow { public: - virtual ~AdHocCommandWindow() {}; + virtual ~AdHocCommandWindow() {} + virtual void setOnline(bool /*online*/) {} + boost::signal<void ()> onClosing; }; } diff --git a/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h b/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h index ae77180..eeefa2d 100644 --- a/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -11,12 +11,9 @@ namespace Swift { +class AdHocCommandWindow; class AdHocCommandWindowFactory { public: virtual ~AdHocCommandWindowFactory() {} - /** - * The UI should deal with the lifetime of this window (i.e. DeleteOnClose), - * so the result isn't returned. - */ - virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) = 0; + virtual AdHocCommandWindow* createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) = 0; }; } diff --git a/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h b/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h new file mode 100644 index 0000000..f8a4c59 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> + +#include <Swiften/JID/JID.h> +#include <Swiften/Base/boost_bsignals.h> + +namespace Swift { + + class ClientBlockListManager; + + class BlockListEditorWidget { + public: + virtual ~BlockListEditorWidget() {} + + virtual void show() = 0; + virtual void hide() = 0; + + virtual void setCurrentBlockList(const std::vector<JID>& blockedJIDs) = 0; + virtual void setBusy(bool isBusy) = 0; + virtual void setError(const std::string&) = 0; + + virtual std::vector<JID> getCurrentBlockList() const = 0; + + boost::signal<void (const std::vector<JID>& /* blockedJID */)> onSetNewBlockList; + }; + +} diff --git a/Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h b/Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h new file mode 100644 index 0000000..eb91ac1 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +namespace Swift { + + class BlockListEditorWidget; + + class BlockListEditorWidgetFactory { + public: + virtual ~BlockListEditorWidgetFactory() {} + + virtual BlockListEditorWidget* createBlockListEditorWidget() = 0; + }; + +} diff --git a/Swift/Controllers/UIInterfaces/ChatListWindow.h b/Swift/Controllers/UIInterfaces/ChatListWindow.h index d047f8c..5a7b527 100644 --- a/Swift/Controllers/UIInterfaces/ChatListWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatListWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -8,9 +8,11 @@ #include <list> +#include <set> +#include <map> #include <boost/shared_ptr.hpp> #include <Swiften/MUC/MUCBookmark.h> #include <Swiften/Elements/StatusShow.h> #include <boost/filesystem/path.hpp> - +#include <Swiften/Base/foreach.h> #include <Swiften/Base/boost_bsignals.h> @@ -20,11 +22,29 @@ namespace Swift { class Chat { public: - Chat(const JID& jid, const std::string& chatName, const std::string& activity, int unreadCount, StatusShow::Type statusType, const boost::filesystem::path& avatarPath, bool isMUC, const std::string& nick = "") - : jid(jid), chatName(chatName), activity(activity), statusType(statusType), isMUC(isMUC), nick(nick), unreadCount(unreadCount), avatarPath(avatarPath) {} + Chat() : statusType(StatusShow::None), isMUC(false), unreadCount(0), isPrivateMessage(false) {} + Chat(const JID& jid, const std::string& chatName, const std::string& activity, int unreadCount, StatusShow::Type statusType, const boost::filesystem::path& avatarPath, bool isMUC, bool isPrivateMessage = false, const std::string& nick = "", const boost::optional<std::string> password = boost::optional<std::string>()) + : jid(jid), chatName(chatName), activity(activity), statusType(statusType), isMUC(isMUC), nick(nick), password(password), unreadCount(unreadCount), avatarPath(avatarPath), isPrivateMessage(isPrivateMessage) {} /** Assume that nicks and other transient features aren't important for equality */ bool operator==(const Chat& other) const { + if (impromptuJIDs.empty()) { return jid.toBare() == other.jid.toBare() && isMUC == other.isMUC; - }; + } else { /* compare the chat occupant lists */ + typedef std::map<std::string, JID> JIDMap; + foreach (const JIDMap::value_type& jid, impromptuJIDs) { + bool found = false; + foreach (const JIDMap::value_type& otherJID, other.impromptuJIDs) { + if (jid.second.toBare() == otherJID.second.toBare()) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; + } + } void setUnreadCount(int unread) { unreadCount = unread; @@ -36,4 +56,16 @@ namespace Swift { avatarPath = path; } + std::string getImpromptuTitle() const { + typedef std::pair<std::string, JID> StringJIDPair; + std::string title; + foreach(StringJIDPair pair, impromptuJIDs) { + if (title.empty()) { + title += pair.first; + } else { + title += ", " + pair.first; + } + } + return title; + } JID jid; std::string chatName; @@ -42,6 +74,9 @@ namespace Swift { bool isMUC; std::string nick; + boost::optional<std::string> password; int unreadCount; boost::filesystem::path avatarPath; + std::map<std::string, JID> impromptuJIDs; + bool isPrivateMessage; }; virtual ~ChatListWindow(); @@ -49,11 +84,15 @@ 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; virtual void clearBookmarks() = 0; + virtual void setOnline(bool isOnline) = 0; 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 b5b1604..6b2799b 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,15 +7,19 @@ #pragma once +#include <vector> +#include <string> + #include <boost/optional.hpp> -#include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> +#include <boost/make_shared.hpp> #include <boost/date_time/posix_time/posix_time.hpp> -#include <vector> -#include <string> +#include <Swiften/Base/boost_bsignals.h> #include <Swiften/Elements/SecurityLabelsCatalog.h> #include <Swiften/Elements/ChatState.h> #include <Swiften/Elements/Form.h> #include <Swiften/Elements/MUCOccupant.h> +#include <Swiften/MUC/MUCBookmark.h> +#include <Swift/Controllers/HighlightManager.h> @@ -28,29 +32,87 @@ namespace Swift { class ContactRosterItem; class FileTransferController; + class UserSearchWindow; + class ChatWindow { public: + class ChatMessagePart { + public: + virtual ~ChatMessagePart() {} + }; + + class ChatMessage { + public: + ChatMessage() {} + ChatMessage(const std::string& text) { + append(boost::make_shared<ChatTextMessagePart>(text)); + } + void append(const boost::shared_ptr<ChatMessagePart>& part) { + parts_.push_back(part); + } + + const std::vector<boost::shared_ptr<ChatMessagePart> >& getParts() const { + return parts_; + } + private: + std::vector<boost::shared_ptr<ChatMessagePart> > parts_; + }; + + class ChatTextMessagePart : public ChatMessagePart { + public: + ChatTextMessagePart(const std::string& text) : text(text) {} + std::string text; + }; + + class ChatURIMessagePart : public ChatMessagePart { + public: + ChatURIMessagePart(const std::string& target) : target(target) {} + std::string target; + }; + + class ChatEmoticonMessagePart : public ChatMessagePart { + public: + std::string imagePath; + std::string alternativeText; + }; + + class ChatHighlightingMessagePart : public ChatMessagePart { + public: + std::string foregroundColor; + std::string backgroundColor; + std::string text; + }; + + enum AckState {Pending, Received, Failed}; - enum ReceiptState {ReceiptRequested, ReceiptReceived}; + enum ReceiptState {ReceiptRequested, ReceiptReceived, ReceiptFailed}; enum Tristate {Yes, No, Maybe}; - enum OccupantAction {Kick, Ban, MakeModerator, MakeParticipant, MakeVisitor, AddContact}; + enum OccupantAction {Kick, Ban, MakeModerator, MakeParticipant, MakeVisitor, AddContact, ShowProfile}; enum RoomAction {ChangeSubject, Configure, Affiliations, Destroy, Invite}; enum FileTransferState {WaitingForAccept, Negotiating, Transferring, Canceled, Finished, FTFailed}; + enum WhiteboardSessionState {WhiteboardAccepted, WhiteboardTerminated, WhiteboardRejected}; + enum BlockingState {BlockingUnsupported, IsBlocked, IsUnblocked}; + enum Direction { UnknownDirection, DefaultDirection }; + enum MUCType { StandardMUC, ImpromptuMUC }; + enum TimestampBehaviour { KeepTimestamp, UpdateTimestamp }; + ChatWindow() {} - virtual ~ChatWindow() {}; + virtual ~ChatWindow() {} /** Add message to window. * @return id of added message (for acks). */ - virtual std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) = 0; + virtual std::string addMessage(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; /** Adds action to window. * @return id of added message (for acks); */ - virtual std::string addAction(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) = 0; - virtual void addSystemMessage(const std::string& message) = 0; - virtual void addPresenceMessage(const std::string& message) = 0; - virtual void addErrorMessage(const std::string& message) = 0; - virtual void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) = 0; - virtual void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) = 0; + virtual std::string addAction(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; + + virtual void addSystemMessage(const ChatMessage& message, Direction direction) = 0; + virtual void addPresenceMessage(const ChatMessage& message, Direction direction) = 0; + + virtual void addErrorMessage(const ChatMessage& message) = 0; + virtual void replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; + virtual void replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; // File transfer related stuff @@ -58,5 +120,8 @@ namespace Swift { virtual void setFileTransferProgress(std::string, const int percentageDone) = 0; 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 void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true, bool isImpromptu = false, bool isContinuation = false) = 0; + + virtual std::string addWhiteboardRequest(bool senderIsSelf) = 0; + virtual void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) = 0; // message receipts @@ -66,10 +131,12 @@ namespace Swift { virtual void setName(const std::string& name) = 0; virtual void show() = 0; + virtual bool isVisible() const = 0; virtual void activate() = 0; virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) = 0; virtual void setSecurityLabelsEnabled(bool enabled) = 0; virtual void setCorrectionEnabled(Tristate enabled) = 0; + virtual void setFileTransferEnabled(Tristate enabled) = 0; virtual void setUnreadMessageCount(int count) = 0; - virtual void convertToMUC() = 0; + virtual void convertToMUC(MUCType mucType) = 0; // virtual TreeWidget *getTreeWidget() = 0; virtual void setSecurityLabelsError() = 0; @@ -78,5 +145,5 @@ namespace Swift { virtual void setRosterModel(Roster* model) = 0; virtual void setTabComplete(TabComplete* completer) = 0; - virtual void replaceLastMessage(const std::string& message) = 0; + virtual void replaceLastMessage(const ChatMessage& message, const TimestampBehaviour timestampBehaviour) = 0; virtual void setAckState(const std::string& id, AckState state) = 0; virtual void flash() = 0; @@ -84,14 +151,24 @@ namespace Swift { virtual void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&) = 0; virtual void setAvailableRoomActions(const std::vector<RoomAction> &actions) = 0; + virtual void setBlockingState(BlockingState state) = 0; + virtual void setCanInitiateImpromptuChats(bool supportsImpromptu) = 0; + virtual void showBookmarkWindow(const MUCBookmark& bookmark) = 0; + + /** + * A handle that uniquely identities an alert message. + */ + typedef int AlertID; /** * Set an alert on the window. * @param alertText Description of alert (required). * @param buttonText Button text to use (optional, no button is shown if empty). + * @return A handle to the alert message. */ - virtual void setAlert(const std::string& alertText, const std::string& buttonText = "") = 0; + virtual AlertID addAlert(const std::string& alertText) = 0; /** * Removes an alert. + * @param id An alert ID previously returned from setAlert */ - virtual void cancelAlert() = 0; + virtual void removeAlert(const AlertID id) = 0; /** @@ -116,7 +193,8 @@ namespace Swift { boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected; boost::signal<void (const std::string&)> onChangeSubjectRequest; + boost::signal<void ()> onBookmarkRequest; boost::signal<void (Form::ref)> onConfigureRequest; boost::signal<void ()> onDestroyRequest; - boost::signal<void (const JID&, const std::string& /*reason*/)> onInvitePersonToThisMUCRequest; + boost::signal<void (const std::vector<JID>&)> onInviteToChat; boost::signal<void ()> onConfigurationFormCancelled; boost::signal<void ()> onGetAffiliationsRequest; @@ -130,4 +208,13 @@ namespace Swift { 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; + + // Blocking Command related + boost::signal<void ()> onBlockUserRequest; + boost::signal<void ()> onUnblockUserRequest; }; } diff --git a/Swift/Controllers/UIInterfaces/ChatWindowFactory.h b/Swift/Controllers/UIInterfaces/ChatWindowFactory.h index b7b4479..62e6621 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/ChatWindowFactory.h @@ -15,5 +15,5 @@ namespace Swift { class ChatWindowFactory { public: - virtual ~ChatWindowFactory() {}; + virtual ~ChatWindowFactory() {} /** * Transfers ownership of result. diff --git a/Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h b/Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h index 8ad56c0..9d47aef 100644 --- a/Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h @@ -12,5 +12,5 @@ namespace Swift { class ContactEditWindowFactory { public: - virtual ~ContactEditWindowFactory() {}; + virtual ~ContactEditWindowFactory() {} virtual ContactEditWindow* createContactEditWindow() = 0; diff --git a/Swift/Controllers/UIInterfaces/EventWindow.h b/Swift/Controllers/UIInterfaces/EventWindow.h index 3ca2c82..b3af3d3 100644 --- a/Swift/Controllers/UIInterfaces/EventWindow.h +++ b/Swift/Controllers/UIInterfaces/EventWindow.h @@ -20,5 +20,5 @@ namespace Swift { } - virtual ~EventWindow() {}; + virtual ~EventWindow() {} virtual void addEvent(boost::shared_ptr<StanzaEvent> event, bool active) = 0; virtual void removeEvent(boost::shared_ptr<StanzaEvent> event) = 0; diff --git a/Swift/Controllers/UIInterfaces/EventWindowFactory.h b/Swift/Controllers/UIInterfaces/EventWindowFactory.h index 1ff3ada..0b9c28e 100644 --- a/Swift/Controllers/UIInterfaces/EventWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/EventWindowFactory.h @@ -12,5 +12,5 @@ namespace Swift { class EventWindowFactory { public: - virtual ~EventWindowFactory() {}; + virtual ~EventWindowFactory() {} /** * Transfers ownership of result. diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWidget.h b/Swift/Controllers/UIInterfaces/HighlightEditorWidget.h new file mode 100644 index 0000000..4745035 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWidget.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +namespace Swift { + + class HighlightManager; + + class HighlightEditorWidget { + public: + virtual ~HighlightEditorWidget() {} + + virtual void show() = 0; + + virtual void setHighlightManager(HighlightManager* highlightManager) = 0; + }; + +} diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h b/Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h new file mode 100644 index 0000000..ade575b --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +namespace Swift { + + class HighlightEditorWidget; + + class HighlightEditorWidgetFactory { + public: + virtual ~HighlightEditorWidgetFactory() {} + + virtual HighlightEditorWidget* createHighlightEditorWidget() = 0; + }; + +} diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp new file mode 100644 index 0000000..f90903b --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h> + +namespace Swift { + +HighlightEditorWindow::HighlightEditorWindow() +{ +} + +HighlightEditorWindow::~HighlightEditorWindow() +{ +} + +} diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h new file mode 100644 index 0000000..83ae959 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <vector> +#include <Swiften/Base/boost_bsignals.h> +#include <Swift/Controllers/Contact.h> + +namespace Swift { + +class HighlightManager; + +class HighlightEditorWindow { +public: + HighlightEditorWindow(); + virtual ~HighlightEditorWindow(); + virtual void show() = 0; + virtual void setHighlightManager(HighlightManager *highlightManager) = 0; + virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions) = 0; + +public: + boost::signal<void (const std::string&)> onContactSuggestionsRequested; +}; + +} diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h b/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h new file mode 100644 index 0000000..e0aaaef --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.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. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +namespace Swift { + class HighlightEditorWindow; + + class HighlightEditorWindowFactory { + public : + virtual ~HighlightEditorWindowFactory() {} + + virtual HighlightEditorWindow* createHighlightEditorWindow() = 0; + }; +} diff --git a/Swift/Controllers/UIInterfaces/HistoryWindow.h b/Swift/Controllers/UIInterfaces/HistoryWindow.h new file mode 100644 index 0000000..6d50f4b --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HistoryWindow.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/Roster/Roster.h> + +namespace Swift { + class HistoryWindow { + public: + virtual ~HistoryWindow() {} + + virtual void activate() = 0; + virtual void setRosterModel(Roster*) = 0; + virtual void addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, const std::string& avatarPath, const boost::posix_time::ptime& time, bool addAtTheTop) = 0; + virtual void resetConversationView() = 0; + virtual void resetConversationViewTopInsertPoint() = 0; // this is a temporary fix used in adding messages at the top + virtual void setDate(const boost::gregorian::date& date) = 0; + + virtual std::string getSearchBoxText() = 0; + virtual boost::gregorian::date getLastVisibleDate() = 0; + + boost::signal<void (RosterItem*)> onSelectedContactChanged; + boost::signal<void (const std::string&)> onReturnPressed; + boost::signal<void (const boost::gregorian::date&)> onScrollReachedTop; + boost::signal<void (const boost::gregorian::date&)> onScrollReachedBottom; + boost::signal<void ()> onPreviousButtonClicked; + boost::signal<void ()> onNextButtonClicked; + boost::signal<void (const boost::gregorian::date&)> onCalendarClicked; + }; +} diff --git a/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h b/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h new file mode 100644 index 0000000..807fec5 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIInterfaces/HistoryWindow.h> + +namespace Swift { + class UIEventStream; + class HistoryWindowFactory { + public: + virtual ~HistoryWindowFactory() {} + virtual HistoryWindow* createHistoryWindow(UIEventStream* eventStream) = 0; + }; +} diff --git a/Swift/Controllers/UIInterfaces/JoinMUCWindow.h b/Swift/Controllers/UIInterfaces/JoinMUCWindow.h index 4873c9b..56a9587 100644 --- a/Swift/Controllers/UIInterfaces/JoinMUCWindow.h +++ b/Swift/Controllers/UIInterfaces/JoinMUCWindow.h @@ -16,5 +16,5 @@ namespace Swift { class JoinMUCWindow { public: - virtual ~JoinMUCWindow() {}; + virtual ~JoinMUCWindow() {} virtual void setNick(const std::string& nick) = 0; diff --git a/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h b/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h index cd8021b..494b418 100644 --- a/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h @@ -13,5 +13,5 @@ namespace Swift { class JoinMUCWindowFactory { public: - virtual ~JoinMUCWindowFactory() {}; + virtual ~JoinMUCWindowFactory() {} virtual JoinMUCWindow* createJoinMUCWindow(UIEventStream* uiEventStream) = 0; diff --git a/Swift/Controllers/UIInterfaces/LoginWindow.h b/Swift/Controllers/UIInterfaces/LoginWindow.h index bbbbe35..e27c385 100644 --- a/Swift/Controllers/UIInterfaces/LoginWindow.h +++ b/Swift/Controllers/UIInterfaces/LoginWindow.h @@ -13,4 +13,5 @@ #include <Swiften/TLS/Certificate.h> #include <Swiften/TLS/CertificateWithKey.h> +#include <Swiften/Client/ClientOptions.h> namespace Swift { @@ -18,5 +19,5 @@ namespace Swift { class LoginWindow { public: - virtual ~LoginWindow() {}; + virtual ~LoginWindow() {} virtual void selectUser(const std::string&) = 0; virtual void morphInto(MainWindow *mainWindow) = 0; @@ -25,12 +26,12 @@ namespace Swift { virtual void setMessage(const std::string&) = 0; virtual void setIsLoggingIn(bool loggingIn) = 0; - virtual void addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate) = 0; + virtual void addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate, const ClientOptions& options) = 0; virtual void removeAvailableAccount(const std::string& jid) = 0; /** The certificate is what is used for login, the certificatePath is used for remembering paths to populate the loginwindow with*/ - boost::signal<void (const std::string&, const std::string&, const std::string& /*CertificatePath*/, CertificateWithKey::ref /* clientCertificate */, bool /* remember password*/, bool /* login automatically */)> onLoginRequest; + boost::signal<void (const std::string&, const std::string&, const std::string& /*CertificatePath*/, CertificateWithKey::ref /* clientCertificate */, const ClientOptions& /*options*/, bool /* remember password*/, bool /* login automatically */)> onLoginRequest; virtual void setLoginAutomatically(bool loginAutomatically) = 0; virtual void quit() = 0; /** Blocking request whether a cert should be permanently trusted.*/ - virtual bool askUserToTrustCertificatePermanently(const std::string& message, Certificate::ref) = 0; + virtual bool askUserToTrustCertificatePermanently(const std::string& message, const std::vector<Certificate::ref>& certificateChain) = 0; boost::signal<void ()> onCancelLoginRequest; diff --git a/Swift/Controllers/UIInterfaces/LoginWindowFactory.h b/Swift/Controllers/UIInterfaces/LoginWindowFactory.h index 1cead2a..7b8b7ec 100644 --- a/Swift/Controllers/UIInterfaces/LoginWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/LoginWindowFactory.h @@ -17,5 +17,5 @@ namespace Swift { class LoginWindowFactory { public: - virtual ~LoginWindowFactory() {}; + virtual ~LoginWindowFactory() {} /** diff --git a/Swift/Controllers/UIInterfaces/MUCSearchWindow.h b/Swift/Controllers/UIInterfaces/MUCSearchWindow.h index 5814b06..43a61a1 100644 --- a/Swift/Controllers/UIInterfaces/MUCSearchWindow.h +++ b/Swift/Controllers/UIInterfaces/MUCSearchWindow.h @@ -20,5 +20,5 @@ namespace Swift { class MUCSearchWindow { public: - virtual ~MUCSearchWindow() {}; + virtual ~MUCSearchWindow() {} virtual void clearList() = 0; diff --git a/Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h b/Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h index d334dff..46488eb 100644 --- a/Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h @@ -13,5 +13,5 @@ namespace Swift { class MUCSearchWindowFactory { public: - virtual ~MUCSearchWindowFactory() {}; + virtual ~MUCSearchWindowFactory() {} virtual MUCSearchWindow* createMUCSearchWindow() = 0; diff --git a/Swift/Controllers/UIInterfaces/MainWindow.h b/Swift/Controllers/UIInterfaces/MainWindow.h index 93584e7..82750bf 100644 --- a/Swift/Controllers/UIInterfaces/MainWindow.h +++ b/Swift/Controllers/UIInterfaces/MainWindow.h @@ -8,11 +8,14 @@ #include <string> -#include "Swiften/JID/JID.h" -#include "Swiften/Elements/StatusShow.h" -#include "Swiften/Elements/DiscoItems.h" -#include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/StatusShow.h> +#include <Swiften/Elements/DiscoItems.h> +#include <Swiften/TLS/Certificate.h> +#include <Swiften/Base/boost_bsignals.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + namespace Swift { class Roster; @@ -21,5 +24,5 @@ namespace Swift { public: MainWindow(bool candelete = true) : canDelete_(candelete) {} - virtual ~MainWindow() {}; + virtual ~MainWindow() {} bool canDelete() const { @@ -32,11 +35,16 @@ namespace Swift { virtual void setMyStatusText(const std::string& status) = 0; virtual void setMyStatusType(StatusShow::Type type) = 0; + virtual void setMyContactRosterItem(boost::shared_ptr<ContactRosterItem> contact) = 0; /** Must be able to cope with NULL to clear the roster */ virtual void setRosterModel(Roster* roster) = 0; virtual void setConnecting() = 0; + virtual void setBlockingCommandAvailable(bool isAvailable) = 0; virtual void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands) = 0; + virtual void setStreamEncryptionStatus(bool tlsInPlaceAndValid) = 0; + virtual void openCertificateDialog(const std::vector<Certificate::ref>& chain) = 0; boost::signal<void (StatusShow::Type, const std::string&)> onChangeStatusRequest; boost::signal<void ()> onSignOutRequest; + boost::signal<void ()> onShowCertificateRequest; private: diff --git a/Swift/Controllers/UIInterfaces/MainWindowFactory.h b/Swift/Controllers/UIInterfaces/MainWindowFactory.h index c5cdfef..6bd34b4 100644 --- a/Swift/Controllers/UIInterfaces/MainWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/MainWindowFactory.h @@ -16,5 +16,5 @@ namespace Swift { class MainWindowFactory { public: - virtual ~MainWindowFactory() {}; + virtual ~MainWindowFactory() {} /** * Transfers ownership of result. diff --git a/Swift/Controllers/UIInterfaces/ProfileWindow.h b/Swift/Controllers/UIInterfaces/ProfileWindow.h index 5d5c754..5c158e1 100644 --- a/Swift/Controllers/UIInterfaces/ProfileWindow.h +++ b/Swift/Controllers/UIInterfaces/ProfileWindow.h @@ -13,8 +13,11 @@ namespace Swift { + class JID; + class ProfileWindow { public: - virtual ~ProfileWindow() {}; + virtual ~ProfileWindow() {} + virtual void setJID(const JID& jid) = 0; virtual void setVCard(VCard::ref vcard) = 0; @@ -22,4 +25,5 @@ namespace Swift { virtual void setProcessing(bool b) = 0; virtual void setError(const std::string&) = 0; + virtual void setEditable(bool b) = 0; virtual void show() = 0; @@ -27,4 +31,5 @@ namespace Swift { boost::signal<void (VCard::ref)> onVCardChangeRequest; + boost::signal<void (const JID&)> onWindowAboutToBeClosed; }; } diff --git a/Swift/Controllers/UIInterfaces/ProfileWindowFactory.h b/Swift/Controllers/UIInterfaces/ProfileWindowFactory.h index 022c3eb..45a340a 100644 --- a/Swift/Controllers/UIInterfaces/ProfileWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/ProfileWindowFactory.h @@ -12,5 +12,5 @@ namespace Swift { class ProfileWindowFactory { public: - virtual ~ProfileWindowFactory() {}; + virtual ~ProfileWindowFactory() {} virtual ProfileWindow* createProfileWindow() = 0; diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h index cf89dab..54fa7ce 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -9,4 +9,5 @@ #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/HistoryWindowFactory.h> #include <Swift/Controllers/UIInterfaces/EventWindowFactory.h> #include <Swift/Controllers/UIInterfaces/LoginWindowFactory.h> @@ -20,4 +21,7 @@ #include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> #include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h> +#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h> namespace Swift { @@ -25,4 +29,5 @@ namespace Swift { public ChatListWindowFactory, public ChatWindowFactory, + public HistoryWindowFactory, public EventWindowFactory, public LoginWindowFactory, @@ -35,5 +40,8 @@ namespace Swift { public ContactEditWindowFactory, public AdHocCommandWindowFactory, - public FileTransferListWidgetFactory { + public FileTransferListWidgetFactory, + public WhiteboardWindowFactory, + public HighlightEditorWindowFactory, + public BlockListEditorWidgetFactory { public: virtual ~UIFactory() {} diff --git a/Swift/Controllers/UIInterfaces/UserSearchWindow.h b/Swift/Controllers/UIInterfaces/UserSearchWindow.h index a3d69d6..9a095aa 100644 --- a/Swift/Controllers/UIInterfaces/UserSearchWindow.h +++ b/Swift/Controllers/UIInterfaces/UserSearchWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,11 +7,12 @@ #pragma once -#include "Swiften/Base/boost_bsignals.h" +#include <Swiften/Base/boost_bsignals.h> #include <vector> #include <string> -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Chat/UserSearchController.h" +#include <Swiften/JID/JID.h> +#include <Swift/Controllers/Chat/UserSearchController.h> +#include <Swift/Controllers/Contact.h> namespace Swift { @@ -19,5 +20,5 @@ namespace Swift { class UserSearchWindow { public: - enum Type {AddContact, ChatToContact}; + enum Type {AddContact, ChatToContact, InviteToChat}; virtual ~UserSearchWindow() {} @@ -32,4 +33,14 @@ namespace Swift { virtual void setNameSuggestions(const std::vector<std::string>& suggestions) = 0; virtual void prepopulateJIDAndName(const JID& jid, const std::string& name) = 0; + virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions) = 0; + virtual void setJIDs(const std::vector<JID>&) = 0; + virtual void setRoomJID(const JID& roomJID) = 0; + virtual std::string getReason() const = 0; + virtual std::vector<JID> getJIDs() const = 0; + virtual void setCanStartImpromptuChats(bool supportsImpromptu) = 0; + virtual void updateContacts(const std::vector<Contact::ref>& contacts) = 0; + virtual void addContacts(const std::vector<Contact::ref>& contacts) = 0; + virtual void setCanSupplyDescription(bool allowed) = 0; + virtual void show() = 0; @@ -37,4 +48,7 @@ namespace Swift { boost::signal<void (boost::shared_ptr<SearchPayload>, const JID&)> onSearchRequested; boost::signal<void (const JID&)> onNameSuggestionRequested; + boost::signal<void (const std::string&)> onContactSuggestionsRequested; + boost::signal<void (const std::vector<JID>&)> onJIDUpdateRequested; + boost::signal<void (const std::vector<JID>&)> onJIDAddRequested; }; } diff --git a/Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h b/Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h index 2a15806..331d6dd 100644 --- a/Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h @@ -15,5 +15,5 @@ namespace Swift { class UserSearchWindowFactory { public: - virtual ~UserSearchWindowFactory() {}; + virtual ~UserSearchWindowFactory() {} virtual UserSearchWindow* createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups) = 0; 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..2be0f9c --- /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/UIInterfaces/XMLConsoleWidgetFactory.h b/Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h index e27fe2e..3cba597 100644 --- a/Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h +++ b/Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h @@ -13,5 +13,5 @@ namespace Swift { class XMLConsoleWidgetFactory { public: - virtual ~XMLConsoleWidgetFactory() {}; + virtual ~XMLConsoleWidgetFactory() {} virtual XMLConsoleWidget* createXMLConsoleWidget() = 0; diff --git a/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp b/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp index ee0ee9f..dd2a8e5 100644 --- a/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp +++ b/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp @@ -27,5 +27,5 @@ class ChatMessageSummarizerTest : public CppUnit::TestFixture { public: - ChatMessageSummarizerTest() {}; + ChatMessageSummarizerTest() {} void setUp() { @@ -73,5 +73,5 @@ public: unreads.push_back(UnreadPair("Betty", 7)); ChatMessageSummarizer summary; - CPPUNIT_ASSERT_EQUAL(string("Bob (3), Betty (7)"), summary.getSummary(current, unreads)); + CPPUNIT_ASSERT_EQUAL(string("Bob (3); Betty (7)"), summary.getSummary(current, unreads)); } @@ -83,5 +83,5 @@ public: unreads.push_back(UnreadPair("Betty", 7)); ChatMessageSummarizer summary; - CPPUNIT_ASSERT_EQUAL(string("Bob, Betty (7)"), summary.getSummary(current, unreads)); + CPPUNIT_ASSERT_EQUAL(string("Bob; Betty (7)"), summary.getSummary(current, unreads)); } diff --git a/Swift/Controllers/UnitTest/ContactSuggesterTest.cpp b/Swift/Controllers/UnitTest/ContactSuggesterTest.cpp new file mode 100644 index 0000000..222491b --- /dev/null +++ b/Swift/Controllers/UnitTest/ContactSuggesterTest.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2014 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <boost/bind.hpp> +#include <boost/function.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include "Swift/Controllers/ContactSuggester.h" + +using namespace Swift; + +class ContactSuggesterTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(ContactSuggesterTest); + CPPUNIT_TEST(equalityTest); + CPPUNIT_TEST(lexicographicalSortTest); + CPPUNIT_TEST(sortTest); + CPPUNIT_TEST_SUITE_END(); + +public: + + std::vector<std::string> wordList() { + const std::string words[] = { + "abc", + "ab", + "bc", + "d" + }; + + return std::vector<std::string>(words, words+sizeof(words)/sizeof(*words)); + } + + std::vector<StatusShow::Type> statusList() { + StatusShow::Type types[] = { + StatusShow::Online, + StatusShow::Away, + StatusShow::FFC, + StatusShow::XA, + StatusShow::DND, + StatusShow::None + }; + + return std::vector<StatusShow::Type>(types, types+sizeof(types)/sizeof(*types)); + } + + std::vector<Contact::ref> contactList() { + std::vector<Contact::ref> contacts; + std::vector<std::string> words = wordList(); + std::vector<StatusShow::Type> statuses = statusList(); + foreach (const std::string& name, words) { + foreach (const std::string& jid, words) { + foreach (const StatusShow::Type& status, statuses) { + contacts.push_back(boost::make_shared<Contact>(name, jid, status, "")); + } + } + } + return contacts; + } + + /* a = a */ + bool isReflexive(const boost::function<bool (const Contact::ref&, const Contact::ref&)>& comparitor) { + std::vector<Contact::ref> contacts = contactList(); + foreach (const Contact::ref& a, contacts) { + if (!comparitor(a, a)) { + return false; + } + } + return true; + } + + /* a = b -> b = a */ + bool isSymmetric(const boost::function<bool (const Contact::ref&, const Contact::ref&)>& comparitor) { + std::vector<Contact::ref> contacts = contactList(); + foreach (const Contact::ref& a, contacts) { + foreach (const Contact::ref& b, contacts) { + if (comparitor(a, b)) { + if (!comparitor(b, a)) { + return false; + } + } + } + } + return true; + } + + /* a = b && b = c -> a = c */ + bool isTransitive(const boost::function<bool (const Contact::ref&, const Contact::ref&)>& comparitor) { + std::vector<Contact::ref> contacts = contactList(); + foreach (const Contact::ref& a, contacts) { + foreach (const Contact::ref& b, contacts) { + foreach (const Contact::ref& c, contacts) { + if (comparitor(a, b) && comparitor(b, c)) { + if (!comparitor(a, c)) { + return false; + } + } + } + } + } + return true; + } + + void equalityTest() { + CPPUNIT_ASSERT(isReflexive(Contact::equalityPredicate)); + CPPUNIT_ASSERT(isSymmetric(Contact::equalityPredicate)); + CPPUNIT_ASSERT(isTransitive(Contact::equalityPredicate)); + } + + void lexicographicalSortTest() { + CPPUNIT_ASSERT(isTransitive(Contact::lexicographicalSortPredicate)); + } + + void sortTest() { + std::vector<std::string> words = wordList(); + foreach (const std::string& word, words) { + CPPUNIT_ASSERT(isTransitive(boost::bind(Contact::sortPredicate, _1, _2, word))); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ContactSuggesterTest); diff --git a/Swift/Controllers/UnitTest/HighlightRuleTest.cpp b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp new file mode 100644 index 0000000..c988b8d --- /dev/null +++ b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <vector> +#include <string> + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <Swift/Controllers/HighlightRule.h> + +using namespace Swift; + +class HighlightRuleTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(HighlightRuleTest); + CPPUNIT_TEST(testEmptyRuleNeverMatches); + CPPUNIT_TEST(testKeyword); + CPPUNIT_TEST(testNickKeyword); + CPPUNIT_TEST(testNickWithoutOtherKeywords); + CPPUNIT_TEST(testSender); + CPPUNIT_TEST(testSenderAndKeyword); + CPPUNIT_TEST(testWholeWords); + CPPUNIT_TEST(testCase); + CPPUNIT_TEST(testWholeWordsAndCase); + CPPUNIT_TEST(testMUC); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + std::vector<std::string> keywords; + keywords.push_back("keyword1"); + keywords.push_back("KEYWORD2"); + + std::vector<std::string> senders; + senders.push_back("sender1"); + senders.push_back("SENDER2"); + + emptyRule = new HighlightRule(); + + keywordRule = new HighlightRule(); + keywordRule->setKeywords(keywords); + + keywordChatRule = new HighlightRule(); + keywordChatRule->setKeywords(keywords); + keywordChatRule->setMatchChat(true); + + keywordNickChatRule = new HighlightRule(); + keywordNickChatRule->setKeywords(keywords); + keywordNickChatRule->setNickIsKeyword(true); + keywordNickChatRule->setMatchChat(true); + + nickChatRule = new HighlightRule(); + nickChatRule->setNickIsKeyword(true); + nickChatRule->setMatchChat(true); + + nickRule = new HighlightRule(); + nickRule->setNickIsKeyword(true); + + senderRule = new HighlightRule(); + senderRule->setSenders(senders); + + senderChatRule = new HighlightRule(); + senderChatRule->setSenders(senders); + senderChatRule->setMatchChat(true); + + senderKeywordChatRule = new HighlightRule(); + senderKeywordChatRule->setSenders(senders); + senderKeywordChatRule->setKeywords(keywords); + senderKeywordChatRule->setMatchChat(true); + + senderKeywordNickChatRule = new HighlightRule(); + senderKeywordNickChatRule->setSenders(senders); + senderKeywordNickChatRule->setKeywords(keywords); + senderKeywordNickChatRule->setNickIsKeyword(true); + senderKeywordNickChatRule->setMatchChat(true); + + senderKeywordNickWordChatRule = new HighlightRule(); + senderKeywordNickWordChatRule->setSenders(senders); + senderKeywordNickWordChatRule->setKeywords(keywords); + senderKeywordNickWordChatRule->setNickIsKeyword(true); + senderKeywordNickWordChatRule->setMatchWholeWords(true); + senderKeywordNickWordChatRule->setMatchChat(true); + + senderKeywordNickCaseChatRule = new HighlightRule(); + senderKeywordNickCaseChatRule->setSenders(senders); + senderKeywordNickCaseChatRule->setKeywords(keywords); + senderKeywordNickCaseChatRule->setNickIsKeyword(true); + senderKeywordNickCaseChatRule->setMatchCase(true); + senderKeywordNickCaseChatRule->setMatchChat(true); + + senderKeywordNickCaseWordChatRule = new HighlightRule(); + senderKeywordNickCaseWordChatRule->setSenders(senders); + senderKeywordNickCaseWordChatRule->setKeywords(keywords); + senderKeywordNickCaseWordChatRule->setNickIsKeyword(true); + senderKeywordNickCaseWordChatRule->setMatchCase(true); + senderKeywordNickCaseWordChatRule->setMatchWholeWords(true); + senderKeywordNickCaseWordChatRule->setMatchChat(true); + + senderKeywordNickMUCRule = new HighlightRule(); + senderKeywordNickMUCRule->setSenders(senders); + senderKeywordNickMUCRule->setKeywords(keywords); + senderKeywordNickMUCRule->setNickIsKeyword(true); + senderKeywordNickMUCRule->setMatchMUC(true); + } + + void tearDown() { + delete emptyRule; + + delete keywordRule; + delete keywordChatRule; + delete keywordNickChatRule; + delete nickChatRule; + delete nickRule; + + delete senderRule; + delete senderChatRule; + delete senderKeywordChatRule; + delete senderKeywordNickChatRule; + + delete senderKeywordNickWordChatRule; + delete senderKeywordNickCaseChatRule; + delete senderKeywordNickCaseWordChatRule; + + delete senderKeywordNickMUCRule; + } + + void testEmptyRuleNeverMatches() { + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "nick", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "nick", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "nick", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "", HighlightRule::MUCMessage), false); + } + + void testKeyword() { + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::MUCMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "sender contains keyword1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc keyword1 xyz", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abckeyword1xyz", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("KEYword1", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc KEYword1 xyz", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abcKEYword1xyz", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword2", "from", "nick", HighlightRule::ChatMessage), false); + } + + void testNickKeyword() { + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::MUCMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body", "sender contains nick", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains mixed-case NiCk", "sender", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("nickname", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("NIckNAME", "from", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body", "from", "", HighlightRule::ChatMessage), false); + } + + void testNickWithoutOtherKeywords() { + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::MUCMessage), false); + CPPUNIT_ASSERT_EQUAL(nickRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body", "sender contains nick but it does't matter", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body contains mixed-case NiCk", "from", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("nickname", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("NIckNAME", "from", "nick", HighlightRule::ChatMessage), true); + + // there are no keywords in this rule and empty nick is not treated as a keyword, so we don't check for keywords to get a match + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body", "from", "", HighlightRule::ChatMessage), true); + } + + void testSender() { + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "sender1", "nick", HighlightRule::MUCMessage), false); + CPPUNIT_ASSERT_EQUAL(senderRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderRule->isMatch("body contains sender1", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abc sender1 xyz", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abcsender1xyz", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "SENDer1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abc SENDer1 xyz", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abcSENDer1xyz", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "sender2", "nick", HighlightRule::ChatMessage), true); + } + + void testSenderAndKeyword() { + CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); + } + + void testWholeWords() { + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("xkeyword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "xsender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), true); + } + + void testCase() { + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("xkeyword1", "xsender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("KEYword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), false); + } + + void testWholeWordsAndCase() { + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("xkeyword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "xsender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("KEYword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), false); + } + + void testMUC() { + CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::MUCMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::MUCMessage), true); + } + + private: + HighlightRule* emptyRule; + + HighlightRule* keywordRule; + HighlightRule* keywordChatRule; + HighlightRule* keywordNickChatRule; + HighlightRule* nickChatRule; + HighlightRule* nickRule; + + HighlightRule* senderRule; + HighlightRule* senderChatRule; + HighlightRule* senderKeywordChatRule; + HighlightRule* senderKeywordNickChatRule; + + HighlightRule* senderKeywordNickWordChatRule; + HighlightRule* senderKeywordNickCaseChatRule; + HighlightRule* senderKeywordNickCaseWordChatRule; + + HighlightRule* senderKeywordNickMUCRule; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(HighlightRuleTest); diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h index e0e18a0..774bdd9 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,52 +7,82 @@ #pragma once -#include "Swift/Controllers/UIInterfaces/ChatWindow.h" +#include <boost/shared_ptr.hpp> + +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swiften/Base/foreach.h> + namespace Swift { class MockChatWindow : public ChatWindow { public: - MockChatWindow() : labelsEnabled_(false) {}; + MockChatWindow() : labelsEnabled_(false) {} virtual ~MockChatWindow(); - virtual std::string addMessage(const std::string& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime&) {lastMessageBody_ = message; return "";}; - virtual std::string addAction(const std::string& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime&) {lastMessageBody_ = message; return "";}; - virtual void addSystemMessage(const std::string& /*message*/) {}; - virtual void addErrorMessage(const std::string& /*message*/) {}; - virtual void addPresenceMessage(const std::string& /*message*/) {}; + virtual std::string addMessage(const ChatMessage& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) { + lastMessageBody_ = bodyFromMessage(message); return "id";} + + virtual std::string addAction(const ChatMessage& /*message*/, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {return "id";} + + virtual void addSystemMessage(const ChatMessage& /*message*/, Direction /*direction*/) {} + virtual void addPresenceMessage(const ChatMessage& /*message*/, Direction /*direction*/) {} + + virtual void addErrorMessage(const ChatMessage& /*message*/) {} + virtual void replaceMessage(const ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {} + virtual void replaceWithAction(const ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {} + virtual void replaceLastMessage(const ChatMessage& /*message*/, const TimestampBehaviour /*timestampBehaviour*/) {} // File transfer related stuff - virtual std::string addFileTransfer(const std::string& /*senderName*/, bool /*senderIsSelf*/,const std::string& /*filename*/, const boost::uintmax_t /*sizeInBytes*/) { return 0; }; - virtual void setFileTransferProgress(std::string /*id*/, const int /*alreadyTransferedBytes*/) { }; - virtual void setFileTransferStatus(std::string /*id*/, const FileTransferState /*state*/, const std::string& /*msg*/) { }; + virtual std::string addFileTransfer(const std::string& /*senderName*/, bool /*senderIsSelf*/,const std::string& /*filename*/, const boost::uintmax_t /*sizeInBytes*/) { return 0; } + virtual void setFileTransferProgress(std::string /*id*/, const int /*alreadyTransferedBytes*/) { } + virtual void setFileTransferStatus(std::string /*id*/, const FileTransferState /*state*/, const std::string& /*msg*/) { } virtual void setMessageReceiptState(const std::string &/* id */, ReceiptState /* state */) { } - virtual void setContactChatState(ChatState::ChatStateType /*state*/) {}; - virtual void setName(const std::string& name) {name_ = name;}; - virtual void show() {}; - virtual void activate() {}; - virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) {labels_ = labels;}; - virtual void setSecurityLabelsEnabled(bool enabled) {labelsEnabled_ = enabled;}; - virtual void setUnreadMessageCount(int /*count*/) {}; - virtual void convertToMUC() {}; - virtual void setSecurityLabelsError() {}; + virtual void setContactChatState(ChatState::ChatStateType /*state*/) {} + virtual void setName(const std::string& name) {name_ = name;} + virtual void show() {} + virtual bool isVisible() const { return true; } + virtual void activate() {} + virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) {labels_ = labels;} + virtual void setSecurityLabelsEnabled(bool enabled) {labelsEnabled_ = enabled;} + virtual void setUnreadMessageCount(int /*count*/) {} + virtual void convertToMUC(MUCType /*mucType*/) {} + virtual void setSecurityLabelsError() {} virtual SecurityLabelsCatalog::Item getSelectedSecurityLabel() {return label_;} - virtual void setInputEnabled(bool /*enabled*/) {}; - virtual void setRosterModel(Roster* /*roster*/) {}; - virtual void setTabComplete(TabComplete*) {}; - virtual void replaceLastMessage(const std::string&) {}; - virtual void replaceMessage(const std::string&, const std::string&, const boost::posix_time::ptime&) {}; - virtual void replaceWithAction(const std::string& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/) {}; - void setAckState(const std::string& /*id*/, AckState /*state*/) {}; - virtual void flash() {}; - virtual void setAlert(const std::string& /*alertText*/, const std::string& /*buttonText*/) {}; - virtual void cancelAlert() {}; + virtual void setInputEnabled(bool /*enabled*/) {} + virtual void setRosterModel(Roster* roster) { roster_ = roster; } + Roster* getRosterModel() { return roster_; } + virtual void setTabComplete(TabComplete*) {} + + void setAckState(const std::string& /*id*/, AckState /*state*/) {} + virtual void flash() {} + virtual AlertID addAlert(const std::string& /*alertText*/) { return 0; } + virtual void removeAlert(const AlertID /*id*/) {} virtual void setCorrectionEnabled(Tristate /*enabled*/) {} + virtual void setFileTransferEnabled(Tristate /*enabled*/) {} void setAvailableOccupantActions(const std::vector<OccupantAction>&/* actions*/) {} 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 void addMUCInvitation(const std::string& /*senderName*/, const JID& /*jid*/, const std::string& /*reason*/, const std::string& /*password*/, bool = true, bool = false, bool = false) {} + + 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 void setAvailableRoomActions(const std::vector<RoomAction> &) {} + + virtual void setBlockingState(BlockingState) {} + virtual void setCanInitiateImpromptuChats(bool /*supportsImpromptu*/) {} + virtual void showBookmarkWindow(const MUCBookmark& /*bookmark*/) {} + + std::string bodyFromMessage(const ChatMessage& message) { + boost::shared_ptr<ChatTextMessagePart> text; + foreach (boost::shared_ptr<ChatMessagePart> part, message.getParts()) { + if ((text = boost::dynamic_pointer_cast<ChatTextMessagePart>(part))) { + return text->text; + } + } + return ""; + } std::string name_; @@ -61,4 +91,5 @@ namespace Swift { bool labelsEnabled_; SecurityLabelsCatalog::Item label_; + Roster* roster_; }; } diff --git a/Swift/Controllers/UnitTest/MockMainWindow.h b/Swift/Controllers/UnitTest/MockMainWindow.h index f773062..b56f352 100644 --- a/Swift/Controllers/UnitTest/MockMainWindow.h +++ b/Swift/Controllers/UnitTest/MockMainWindow.h @@ -7,5 +7,5 @@ #pragma once -#include "Swift/Controllers/UIInterfaces/MainWindow.h" +#include <Swift/Controllers/UIInterfaces/MainWindow.h> namespace Swift { @@ -13,14 +13,18 @@ namespace Swift { class MockMainWindow : public MainWindow { public: - MockMainWindow() : roster(NULL) {}; - virtual ~MockMainWindow() {}; - virtual void setRosterModel(Roster* roster) {this->roster = roster;}; - virtual void setMyNick(const std::string& /*name*/) {};; - virtual void setMyJID(const JID& /*jid*/) {};; - virtual void setMyAvatarPath(const std::string& /*path*/) {}; - virtual void setMyStatusText(const std::string& /*status*/) {}; - virtual void setMyStatusType(StatusShow::Type /*type*/) {}; - virtual void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& /*commands*/) {}; - virtual void setConnecting() {}; + MockMainWindow() : roster(NULL) {} + virtual ~MockMainWindow() {} + virtual void setRosterModel(Roster* roster) {this->roster = roster;} + virtual void setMyNick(const std::string& /*name*/) {} + virtual void setMyJID(const JID& /*jid*/) {} + virtual void setMyAvatarPath(const std::string& /*path*/) {} + virtual void setMyStatusText(const std::string& /*status*/) {} + virtual void setMyStatusType(StatusShow::Type /*type*/) {} + virtual void setMyContactRosterItem(boost::shared_ptr<ContactRosterItem> /*contact*/) {} + virtual void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& /*commands*/) {} + virtual void setConnecting() {} + virtual void setStreamEncryptionStatus(bool /*tlsInPlaceAndValid*/) {} + virtual void openCertificateDialog(const std::vector<Certificate::ref>& /*chain*/) {} + virtual void setBlockingCommandAvailable(bool /*isAvailable*/) {} Roster* roster; diff --git a/Swift/Controllers/UnitTest/MockMainWindowFactory.h b/Swift/Controllers/UnitTest/MockMainWindowFactory.h index d130b39..279a6dd 100644 --- a/Swift/Controllers/UnitTest/MockMainWindowFactory.h +++ b/Swift/Controllers/UnitTest/MockMainWindowFactory.h @@ -14,12 +14,12 @@ namespace Swift { class MockMainWindowFactory : public MainWindowFactory { public: - MockMainWindowFactory() : last(NULL) {}; + MockMainWindowFactory() : last(NULL) {} - virtual ~MockMainWindowFactory() {}; + virtual ~MockMainWindowFactory() {} /** * Transfers ownership of result. */ - virtual MainWindow* createMainWindow(UIEventStream*) {last = new MockMainWindow();return last;}; + virtual MainWindow* createMainWindow(UIEventStream*) {last = new MockMainWindow();return last;} MockMainWindow* last; }; diff --git a/Swift/Controllers/UnitTest/PreviousStatusStoreTest.cpp b/Swift/Controllers/UnitTest/PreviousStatusStoreTest.cpp index 9489e5b..0ccbc4a 100644 --- a/Swift/Controllers/UnitTest/PreviousStatusStoreTest.cpp +++ b/Swift/Controllers/UnitTest/PreviousStatusStoreTest.cpp @@ -31,5 +31,5 @@ public: void tearDown() { - + delete store_; } diff --git a/Swift/Controllers/WhiteboardManager.cpp b/Swift/Controllers/WhiteboardManager.cpp new file mode 100644 index 0000000..d8d89eb --- /dev/null +++ b/Swift/Controllers/WhiteboardManager.cpp @@ -0,0 +1,138 @@ +/* + * 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); + if (window != NULL) { + 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/Controllers/XMPPEvents/ErrorEvent.h b/Swift/Controllers/XMPPEvents/ErrorEvent.h index cbfc471..ac09de9 100644 --- a/Swift/Controllers/XMPPEvents/ErrorEvent.h +++ b/Swift/Controllers/XMPPEvents/ErrorEvent.h @@ -19,8 +19,8 @@ namespace Swift { class ErrorEvent : public StanzaEvent { public: - ErrorEvent(const JID& jid, const std::string& text) : jid_(jid), text_(text){}; - virtual ~ErrorEvent(){}; - const JID& getJID() const {return jid_;}; - const std::string& getText() const {return text_;}; + ErrorEvent(const JID& jid, const std::string& text) : jid_(jid), text_(text){} + virtual ~ErrorEvent(){} + const JID& getJID() const {return jid_;} + const std::string& getText() const {return text_;} private: diff --git a/Swift/Controllers/XMPPEvents/EventController.cpp b/Swift/Controllers/XMPPEvents/EventController.cpp index 9841923..8cb259b 100644 --- a/Swift/Controllers/XMPPEvents/EventController.cpp +++ b/Swift/Controllers/XMPPEvents/EventController.cpp @@ -8,4 +8,5 @@ #include <boost/bind.hpp> +#include <boost/numeric/conversion/cast.hpp> #include <algorithm> @@ -35,5 +36,6 @@ void EventController::handleIncomingEvent(boost::shared_ptr<StanzaEvent> sourceE /* If it's a duplicate subscription request, remove the previous request first */ if (subscriptionEvent) { - foreach(boost::shared_ptr<StanzaEvent> existingEvent, events_) { + EventList existingEvents(events_); + foreach(boost::shared_ptr<StanzaEvent> existingEvent, existingEvents) { boost::shared_ptr<SubscriptionRequestEvent> existingSubscriptionEvent = boost::dynamic_pointer_cast<SubscriptionRequestEvent>(existingEvent); if (existingSubscriptionEvent) { @@ -48,5 +50,5 @@ void EventController::handleIncomingEvent(boost::shared_ptr<StanzaEvent> sourceE events_.push_back(sourceEvent); sourceEvent->onConclusion.connect(boost::bind(&EventController::handleEventConcluded, this, sourceEvent)); - onEventQueueLengthChange(events_.size()); + onEventQueueLengthChange(boost::numeric_cast<int>(events_.size())); onEventQueueEventAdded(sourceEvent); if (sourceEvent->getConcluded()) { @@ -59,5 +61,5 @@ void EventController::handleEventConcluded(boost::shared_ptr<StanzaEvent> event) event->onConclusion.disconnect(boost::bind(&EventController::handleEventConcluded, this, event)); events_.erase(std::remove(events_.begin(), events_.end(), event), events_.end()); - onEventQueueLengthChange(events_.size()); + onEventQueueLengthChange(boost::numeric_cast<int>(events_.size())); } diff --git a/Swift/Controllers/XMPPEvents/MUCInviteEvent.h b/Swift/Controllers/XMPPEvents/MUCInviteEvent.h index 0b430cd..65ecece 100644 --- a/Swift/Controllers/XMPPEvents/MUCInviteEvent.h +++ b/Swift/Controllers/XMPPEvents/MUCInviteEvent.h @@ -14,5 +14,5 @@ namespace Swift { public: - MUCInviteEvent(const JID& inviter, const JID& roomJID, const std::string& reason, const std::string& password, bool direct) : inviter_(inviter), roomJID_(roomJID), reason_(reason), password_(password), direct_(direct) {} + MUCInviteEvent(const JID& inviter, const JID& roomJID, const std::string& reason, const std::string& password, bool direct, bool impromptu) : inviter_(inviter), roomJID_(roomJID), reason_(reason), password_(password), direct_(direct), impromptu_(impromptu) {} const JID& getInviter() const { return inviter_; } @@ -21,4 +21,5 @@ namespace Swift { const std::string& getPassword() const { return password_; } bool getDirect() const { return direct_; } + bool getImpromptu() const { return impromptu_; } private: @@ -28,4 +29,5 @@ namespace Swift { std::string password_; bool direct_; + bool impromptu_; }; } diff --git a/Swift/Controllers/XMPPEvents/MessageEvent.h b/Swift/Controllers/XMPPEvents/MessageEvent.h index 1093470..a9214f5 100644 --- a/Swift/Controllers/XMPPEvents/MessageEvent.h +++ b/Swift/Controllers/XMPPEvents/MessageEvent.h @@ -18,5 +18,5 @@ namespace Swift { typedef boost::shared_ptr<MessageEvent> ref; - MessageEvent(boost::shared_ptr<Message> stanza) : stanza_(stanza), targetsMe_(true) {}; + MessageEvent(boost::shared_ptr<Message> stanza) : stanza_(stanza), targetsMe_(true) {} boost::shared_ptr<Message> getStanza() {return stanza_;} diff --git a/Swift/Controllers/XMPPEvents/StanzaEvent.h b/Swift/Controllers/XMPPEvents/StanzaEvent.h index 321d23d..a15afc1 100644 --- a/Swift/Controllers/XMPPEvents/StanzaEvent.h +++ b/Swift/Controllers/XMPPEvents/StanzaEvent.h @@ -15,11 +15,11 @@ namespace Swift { class StanzaEvent { public: - StanzaEvent() : time_(boost::posix_time::microsec_clock::universal_time()) {concluded_ = false;}; - virtual ~StanzaEvent() {}; - void conclude() {concluded_ = true; onConclusion();}; + StanzaEvent() : time_(boost::posix_time::microsec_clock::universal_time()) {concluded_ = false;} + virtual ~StanzaEvent() {} + void conclude() {concluded_ = true; onConclusion();} /** Do not call this directly from outside the class. * If you connect to this signal, you *must* disconnect from it manually. */ boost::signal<void()> onConclusion; - bool getConcluded() {return concluded_;}; + bool getConcluded() {return concluded_;} boost::posix_time::ptime getTime() {return time_;} private: diff --git a/Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h b/Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h index 1f7812e..fb7a05e 100644 --- a/Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h +++ b/Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h @@ -19,8 +19,8 @@ namespace Swift { class SubscriptionRequestEvent : public StanzaEvent { public: - SubscriptionRequestEvent(const JID& jid, const std::string& reason) : jid_(jid), reason_(reason){}; - virtual ~SubscriptionRequestEvent(){}; - const JID& getJID() const {return jid_;}; - const std::string& getReason() const {return reason_;}; + SubscriptionRequestEvent(const JID& jid, const std::string& reason) : jid_(jid), reason_(reason){} + virtual ~SubscriptionRequestEvent(){} + const JID& getJID() const {return jid_;} + const std::string& getReason() const {return reason_;} boost::signal<void()> onAccept; boost::signal<void()> onDecline; @@ -28,10 +28,10 @@ namespace Swift { onAccept(); conclude(); - }; + } void decline() { onDecline(); conclude(); - }; + } void defer() { |