diff options
| author | Kevin Smith <git@kismith.co.uk> | 2013-06-01 08:26:46 (GMT) | 
|---|---|---|
| committer | Kevin Smith <git@kismith.co.uk> | 2013-08-01 09:22:45 (GMT) | 
| commit | db698bbb6d8c7e878e2cb997e18e572f3646e11d (patch) | |
| tree | 61aa3ff72c64dd8f309a2fe4b31b5b2d3f22f04b | |
| parent | b45602bcd36fb9d2e7a22998434e31014f072d33 (diff) | |
| download | swift-db698bbb6d8c7e878e2cb997e18e572f3646e11d.zip swift-db698bbb6d8c7e878e2cb997e18e572f3646e11d.tar.bz2 | |
Refactor chat messages so parsing of links/emoticons happens in controllers.
Change-Id: I07256f23ffbb6520f5063bdfbed9111946c46746
30 files changed, 702 insertions, 292 deletions
| diff --git a/SwifTools/Linkify.cpp b/SwifTools/Linkify.cpp index 906026d..8ecbb09 100644 --- a/SwifTools/Linkify.cpp +++ b/SwifTools/Linkify.cpp @@ -1,5 +1,5 @@  /* - * 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.   */ @@ -49,4 +49,55 @@ std::string Linkify::linkify(const std::string& input) {  	return std::string(result.str());  } +std::pair<std::vector<std::string>, size_t> Linkify::splitLink(const std::string& input) { +	std::vector<std::string> result; +	std::pair<std::vector<std::string>, size_t> pair; +	std::vector<char> currentURL; +	bool inURL = false; +	size_t urlStartsAt = 0; +	for (size_t i = 0; i < input.size(); ++i) { +		char c = input[i]; +		if (inURL) { +			if (c != ' ' && c != '\t' && c != '\n' && !(c == '*' && i == input.size() - 1 && input[0] == '*')) { +				// Keep parsing +			} +			else { +				std::string url(input.substr(urlStartsAt, i - urlStartsAt)); +				result.push_back(url); +				inURL = false; +				size_t remaining = input.size() - i; +				if (remaining > 0) { +					result.push_back(input.substr(i, remaining)); +				} +				pair.first = result; +				pair.second = urlStartsAt == 0 ? 0 : 1; +				return pair; +			} +		} +		else { +			if (boost::regex_match(input.substr(i, 8), linkifyRegexp)) { +				urlStartsAt = i; +				inURL = true; +				if (i > 0) { +					result.push_back(input.substr(0, i)); +				} +			} +			else { +				// Just keep swimming +			} +		} +	} +	if (urlStartsAt > 0 || inURL) { +		std::string url(input.substr(urlStartsAt, input.size() - urlStartsAt)); +		result.push_back(url); +		pair.first = result; +		pair.second = urlStartsAt == 0 ? 0 : 1; +	} +	else { +		pair.first.push_back(input); +		pair.second = 1; +	} +	return pair; +} +  } diff --git a/SwifTools/Linkify.h b/SwifTools/Linkify.h index ebe232f..0a9c132 100644 --- a/SwifTools/Linkify.h +++ b/SwifTools/Linkify.h @@ -1,15 +1,27 @@  /* - * 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.   */  #pragma once +#include <vector>  #include <string>  namespace Swift {  	namespace Linkify {  		std::string linkify(const std::string&); +		/** +		 * Parse the string for a URI. The string will be split by the URI, and the segments plus index of the URI returned. +		 * If no URI is found the index will be result.size() (i.e. an invalid index) +		 * +		 * Examples: +		 * "not a URI" -> <<"not a URI">, -1> +		 * "http://swift.im" -> <<"http://swift.im">, 0 +		 * " See http://swift.im" -> <<" See ", "http://swift.im">, 1> +		 * "Right, http://swift.im it is" -> <<"Right, ", "http://swift.im", " it is">, 1> + 		 */ +		std::pair<std::vector<std::string>, size_t> splitLink(const std::string& text);  	}  } diff --git a/SwifTools/UnitTest/LinkifyTest.cpp b/SwifTools/UnitTest/LinkifyTest.cpp index 5df1a96..c464b50 100644 --- a/SwifTools/UnitTest/LinkifyTest.cpp +++ b/SwifTools/UnitTest/LinkifyTest.cpp @@ -1,5 +1,5 @@  /* - * 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.   */ @@ -32,6 +32,12 @@ class LinkifyTest : public CppUnit::TestFixture {  		CPPUNIT_TEST(testLinkify_NewLine);  		CPPUNIT_TEST(testLinkify_Tab);  		CPPUNIT_TEST(testLinkify_Action); + +		CPPUNIT_TEST(testLinkify_SplitNone); +		CPPUNIT_TEST(testLinkify_SplitAll); +		CPPUNIT_TEST(testLinkify_SplitFirst); +		CPPUNIT_TEST(testLinkify_SplitSecond); +		CPPUNIT_TEST(testLinkify_SplitMiddle);  		CPPUNIT_TEST_SUITE_END();  	public: @@ -181,12 +187,57 @@ class LinkifyTest : public CppUnit::TestFixture {  		}  		void testLinkify_Action() { -				std::string result = Linkify::linkify("*http://swift.im*"); +			std::string result = Linkify::linkify("*http://swift.im*"); + +			CPPUNIT_ASSERT_EQUAL( +					std::string("*<a href=\"http://swift.im\">http://swift.im</a>*"), +					result); +		} -				CPPUNIT_ASSERT_EQUAL( -						std::string("*<a href=\"http://swift.im\">http://swift.im</a>*"), -						result); +		void checkResult(const std::string& testling, size_t expectedIndex, std::string expectedSplit[]) { +			std::pair<std::vector<std::string>, size_t> result = Linkify::splitLink(testling); +			CPPUNIT_ASSERT_EQUAL(expectedIndex, result.second); +			for (size_t i = 0; i < result.first.size(); i++) { +				CPPUNIT_ASSERT_EQUAL(expectedSplit[i], result.first[i]);  			} +		} + +		void testLinkify_SplitNone() { +			std::string testling = "http this ain't"; +			size_t expectedIndex = 1; +			std::string expectedSplit[] = {"http this ain't"}; +			checkResult(testling, expectedIndex, expectedSplit); +		} + +		void testLinkify_SplitAll() { +			std::string testling = "http://swift.im"; +			size_t expectedIndex = 0; +			std::string expectedSplit[] = {"http://swift.im"}; +			checkResult(testling, expectedIndex, expectedSplit); +		} + +		void testLinkify_SplitFirst() { +			std::string testling = "http://swift.im is a link"; +			size_t expectedIndex = 0; +			std::string expectedSplit[] = {"http://swift.im", " is a link"}; +			checkResult(testling, expectedIndex, expectedSplit); +		} + +		void testLinkify_SplitSecond() { +			std::string testling = "this is a link: http://swift.im"; +			size_t expectedIndex = 1; +			std::string expectedSplit[] = {"this is a link: ", "http://swift.im"}; +			checkResult(testling, expectedIndex, expectedSplit); +		} + +		void testLinkify_SplitMiddle() { +			std::string testling = "Shove a link like http://swift.im in the middle"; +			size_t expectedIndex = 1; +			std::string expectedSplit[] = {"Shove a link like ","http://swift.im", " in the middle"}; +			checkResult(testling, expectedIndex, expectedSplit); +		} + +  };  CPPUNIT_TEST_SUITE_REGISTRATION(LinkifyTest); diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index f5c690c..333ae93 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -1,51 +1,53 @@  /* - * 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.   */ -#include "Swift/Controllers/Chat/ChatController.h" +#include <Swift/Controllers/Chat/ChatController.h>  #include <boost/bind.hpp>  #include <boost/smart_ptr/make_shared.hpp>  #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/Chat/ChatStateNotifier.h>  #include <Swiften/Chat/ChatStateTracker.h>  #include <Swiften/Client/StanzaChannel.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 <Swiften/Base/DateTime.h> +#include <Swiften/Elements/DeliveryReceipt.h> +#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/Elements/Idle.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Client/ClientBlockListManager.h> + +#include <Swift/Controllers/Intl.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/FileTransfer/FileTransferController.h> +#include <Swift/Controllers/StatusUtil.h>  #include <Swift/Controllers/UIEvents/UIEventStream.h>  #include <Swift/Controllers/UIEvents/SendFileUIEvent.h>  #include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h>  #include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h>  #include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h>  #include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h> -#include <Swiften/Elements/DeliveryReceipt.h> -#include <Swiften/Elements/DeliveryReceiptRequest.h> -#include <Swiften/Elements/Idle.h>  #include <Swift/Controllers/SettingConstants.h>  #include <Swift/Controllers/Highlighter.h> -#include <Swiften/Base/Log.h> -#include <Swiften/Client/ClientBlockListManager.h> +  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, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager) -	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) { +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, std::map<std::string, std::string>* emoticons) +	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, emoticons), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) {  	isInMUC_ = isInMUC;  	lastWasPresence_ = false;  	chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider); @@ -77,7 +79,7 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ  	lastShownStatus_ = theirPresence ? theirPresence->getShow() : StatusShow::None;  	chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available);  	startMessage += "."; -	chatWindow_->addSystemMessage(startMessage, ChatWindow::DefaultDirection); +	chatWindow_->addSystemMessage(parseMessageBody(startMessage), ChatWindow::DefaultDirection);  	chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));  	chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));  	chatWindow_->onFileTransferStart.connect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2)); @@ -443,9 +445,9 @@ void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresenc  	std::string newStatusChangeString = getStatusChangeString(newPresence);  	if (newStatusChangeString != lastStatusChangeString_) {  		if (lastWasPresence_) { -			chatWindow_->replaceLastMessage(newStatusChangeString); +			chatWindow_->replaceLastMessage(parseMessageBody(newStatusChangeString));  		} else { -			chatWindow_->addPresenceMessage(newStatusChangeString, ChatWindow::DefaultDirection); +			chatWindow_->addPresenceMessage(parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection);  		}  		lastStatusChangeString_ = newStatusChangeString;  		lastWasPresence_ = true; diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index 17bfdd0..8863c3e 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -1,5 +1,5 @@  /* - * 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.   */ @@ -27,7 +27,7 @@ namespace Swift {  	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, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager); +			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, std::map<std::string, std::string>* emoticons);  			virtual ~ChatController();  			virtual void setToJID(const JID& jid);  			virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index 621fd2d..656133c 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -1,10 +1,10 @@  /* - * 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.   */ -#include "Swift/Controllers/Chat/ChatControllerBase.h" +#include <Swift/Controllers/Chat/ChatControllerBase.h>  #include <sstream>  #include <map> @@ -16,7 +16,6 @@  #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> @@ -25,19 +24,24 @@  #include <Swiften/Elements/MUCInvitationPayload.h>  #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 <Swiften/Base/Regex.h> + +#include <SwifTools/Linkify.h> + +#include <Swift/Controllers/Intl.h> +#include <Swift/Controllers/XMPPEvents/EventController.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>  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, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager) : 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) { +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, std::map<std::string, std::string>* emoticons) : 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), emoticons_(*emoticons) {  	chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);  	chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));  	chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2)); @@ -76,7 +80,7 @@ void ChatControllerBase::createDayChangeTimer() {  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::DefaultDirection); +	chatWindow_->addSystemMessage(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();  } @@ -182,17 +186,17 @@ void ChatControllerBase::activateChatWindow() {  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, pathToString(avatarPath), time, highlight); +		return chatWindow_->addAction(parseMessageBody(String::getSplittedAtFirst(message, ' ').second), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);  	} else { -		return chatWindow_->addMessage(message, senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight); +		return chatWindow_->addMessage(parseMessageBody(message), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);  	}  }  void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {  	if (boost::starts_with(message, "/me ")) { -		chatWindow_->replaceWithAction(String::getSplittedAtFirst(message, ' ').second, id, time, highlight); +		chatWindow_->replaceWithAction(parseMessageBody(String::getSplittedAtFirst(message, ' ').second), id, time, highlight);  	} else { -		chatWindow_->replaceMessage(message, id, time, highlight); +		chatWindow_->replaceMessage(parseMessageBody(message), id, time, highlight);  	}  } @@ -214,7 +218,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m  	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(parseMessageBody(errorMessage));  		}  	}  	else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) { @@ -239,7 +243,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m  			boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();  			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::DefaultDirection); +			chatWindow_->addSystemMessage(parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection);  		}  		boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>(); @@ -345,6 +349,91 @@ void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) {  	handleGeneralMUCInvitation(inviteEvent);  } +typedef std::pair<std::string, std::string> StringPair; + +ChatWindow::ChatMessage ChatControllerBase::parseMessageBody(const std::string& body) { +	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)); +				} +			} +		} +	} +	 + + +	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 */ +		regexString += regexString.empty() ? "(" : "|"; +		regexString += Regex::escape(emoticon.first); +	} +	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)) { +						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::ChatEmoticonMessagePart> emoticonPart = boost::make_shared<ChatWindow::ChatEmoticonMessagePart>(); +						std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(match.str()); +						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; +}  } diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index 0199142..3c527ad 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -1,5 +1,5 @@  /* - * 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.   */ @@ -8,33 +8,35 @@  #include <map>  #include <vector> +#include <string> +  #include <boost/shared_ptr.hpp> -#include "Swiften/Base/boost_bsignals.h"  #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 <Swiften/MUC/MUCRegistry.h>  #include <Swift/Controllers/HighlightManager.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h>  namespace Swift {  	class IQRouter;  	class StanzaChannel; -	class ChatWindow;  	class ChatWindowFactory;  	class AvatarManager;  	class UIEventStream; @@ -63,7 +65,7 @@ namespace Swift {  			void handleCapsChanged(const JID& jid);  		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, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager); +			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, std::map<std::string, std::string>* emoticons);  			/**  			 * Pass the Message appended, and the stanza used to send it. @@ -84,6 +86,7 @@ namespace Swift {  			/** 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; +			ChatWindow::ChatMessage parseMessageBody(const std::string& body);  		private:  			IDGenerator idGenerator_; @@ -120,5 +123,6 @@ namespace Swift {  			HistoryController* historyController_;  			MUCRegistry* mucRegistry_;  			Highlighter* highlighter_; +			const std::map<std::string, std::string>& emoticons_;  	};  } diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index d6010e9..5dd53c1 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -1,16 +1,30 @@  /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2013 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 <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 <Swift/Controllers/Chat/ChatController.h>  #include <Swift/Controllers/Chat/ChatControllerBase.h>  #include <Swift/Controllers/Chat/MUCSearchController.h> @@ -25,25 +39,12 @@  #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 <Swiften/Client/ClientBlockListManager.h>  namespace Swift { @@ -77,7 +78,8 @@ ChatsManager::ChatsManager(  		HistoryController* historyController,  		WhiteboardManager* whiteboardManager,  		HighlightManager* highlightManager, -		ClientBlockListManager* clientBlockListManager) : +		ClientBlockListManager* clientBlockListManager, +		std::map<std::string, std::string>* emoticons) :  			jid_(jid),   			joinMUCWindowFactory_(joinMUCWindowFactory),   			useDelayForLatency_(useDelayForLatency),  @@ -91,7 +93,8 @@ ChatsManager::ChatsManager(  			historyController_(historyController),  			whiteboardManager_(whiteboardManager),  			highlightManager_(highlightManager), -			clientBlockListManager_(clientBlockListManager) { +			clientBlockListManager_(clientBlockListManager), +			emoticons_(emoticons) {  	timerFactory_ = timerFactory;  	eventController_ = eventController;  	stanzaChannel_ = stanzaChannel; @@ -526,7 +529,7 @@ 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_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_); +	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_, emoticons_);  	chatControllers_[contact] = controller;  	controller->setAvailableServerFeatures(serverDiscoInfo_);  	controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false)); @@ -599,7 +602,7 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional  		if (createAsReservedIfNew) {  			muc->setCreateAsReservedIfNew();  		} -		MUCController* controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_); +		MUCController* controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, emoticons_);  		mucControllers_[mucJID] = controller;  		controller->setAvailableServerFeatures(serverDiscoInfo_);  		controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller)); diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index 4d7e9a8..4d1266f 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -1,5 +1,5 @@  /* - * Copyright (c) 2010-2011 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,19 +7,21 @@  #pragma once  #include <map> +#include <string>  #include <boost/shared_ptr.hpp> -#include <string>  #include <Swiften/Elements/DiscoInfo.h>  #include <Swiften/Elements/Message.h>  #include <Swiften/Elements/Presence.h>  #include <Swiften/JID/JID.h>  #include <Swiften/MUC/MUCRegistry.h> +#include <Swiften/MUC/MUCBookmark.h> +  #include <Swift/Controllers/UIEvents/UIEventStream.h>  #include <Swift/Controllers/UIInterfaces/ChatListWindow.h>  #include <Swift/Controllers/UIInterfaces/ChatWindow.h> -#include <Swiften/MUC/MUCBookmark.h> +  namespace Swift {  	class EventController; @@ -55,7 +57,7 @@ namespace Swift {  	class ChatsManager {  		public: -			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager); +			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, std::map<std::string, std::string>* emoticons);  			virtual ~ChatsManager();  			void setAvatarManager(AvatarManager* avatarManager);  			void setOnline(bool enabled); @@ -140,5 +142,6 @@ namespace Swift {  			WhiteboardManager* whiteboardManager_;  			HighlightManager* highlightManager_;  			ClientBlockListManager* clientBlockListManager_; +			std::map<std::string, std::string>* emoticons_;  	};  } diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index 6bf3e5f..0033297 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -1,5 +1,5 @@  /* - * 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.   */ @@ -64,8 +64,9 @@ MUCController::MUCController (  		XMPPRoster* roster,  		HistoryController* historyController,  		MUCRegistry* mucRegistry, -		HighlightManager* highlightManager) : -			ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0) { +		HighlightManager* highlightManager, +		std::map<std::string, std::string>* emoticons) : +			ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, emoticons), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0) {  	parting_ = true;  	joined_ = false;  	lastWasPresence_ = false; @@ -226,7 +227,7 @@ const std::string& MUCController::getNick() {  void MUCController::handleJoinTimeoutTick() {  	receivedActivity(); -	chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString()), ChatWindow::DefaultDirection); +	chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection);  }  void MUCController::receivedActivity() { @@ -277,7 +278,7 @@ 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(parseMessageBody(errorMessage));  	parting_ = true;  	if (!rejoinNick.empty() && renameCounter_ < 10) {  		renameCounter_++; @@ -294,7 +295,7 @@ void MUCController::handleJoinComplete(const std::string& nick) {  	joined_ = true;  	std::string joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick);  	setNick(nick); -	chatWindow_->addSystemMessage(joinMessage, ChatWindow::DefaultDirection); +	chatWindow_->addSystemMessage(parseMessageBody(joinMessage), ChatWindow::DefaultDirection);  #ifdef SWIFT_EXPERIMENTAL_HISTORY  	addRecentLogs(); @@ -360,7 +361,7 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) {  void MUCController::addPresenceMessage(const std::string& message) {  	lastWasPresence_ = true; -	chatWindow_->addPresenceMessage(message, ChatWindow::DefaultDirection); +	chatWindow_->addPresenceMessage(parseMessageBody(message), ChatWindow::DefaultDirection);  } @@ -447,7 +448,7 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes  	joined_ = true;  	if (message->hasSubject() && message->getBody().empty()) { -		chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject()), ChatWindow::DefaultDirection);; +		chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject())), ChatWindow::DefaultDirection);;  		chatWindow_->setSubject(message->getSubject());  		doneGettingHistory_ = true;  	} @@ -484,7 +485,7 @@ void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUC  	std::string group(roleToGroupName(occupant.getRole()));  	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())), ChatWindow::DefaultDirection); +	chatWindow_->addSystemMessage(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());  	} @@ -517,7 +518,7 @@ void MUCController::setOnline(bool online) {  	} else {  		if (shouldJoinOnReconnect_) {  			renameCounter_ = 0; -			chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString()), ChatWindow::DefaultDirection); +			chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection);  			if (loginCheckTimer_) {  				loginCheckTimer_->start();  			} @@ -615,7 +616,7 @@ boost::optional<boost::posix_time::ptime> MUCController::getMessageTimestamp(boo  }  void MUCController::updateJoinParts() { -	chatWindow_->replaceLastMessage(generateJoinPartString(joinParts_)); +	chatWindow_->replaceLastMessage(parseMessageBody(generateJoinPartString(joinParts_)));  }  void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent) { @@ -735,13 +736,13 @@ void MUCController::handleConfigureRequest(Form::ref form) {  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(parseMessageBody(errorMessage));  }  void MUCController::handleOccupantRoleChangeFailed(ErrorPayload::ref error, const JID&, MUCOccupant::Role) {  	std::string errorMessage = getErrorMessage(error);  	errorMessage = str(format(QT_TRANSLATE_NOOP("", "Occupant role change failed: %1%.")) % errorMessage); -	chatWindow_->addErrorMessage(errorMessage); +	chatWindow_->addErrorMessage(parseMessageBody(errorMessage));  }  void MUCController::handleConfigurationFormReceived(Form::ref form) { diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index 656eadb..57fd9cd 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -1,24 +1,27 @@  /* - * 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.   */  #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>  #include <Swiften/JID/JID.h>  #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> @@ -45,7 +48,7 @@ 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, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager); +			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, std::map<std::string, std::string>* emoticons);  			~MUCController();  			boost::signal<void ()> onUserLeft;  			boost::signal<void ()> onUserJoined; diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index 84a407c..06c1b84 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -111,7 +111,8 @@ public:  		mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_);  		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_ = new std::map<std::string, std::string>(); +		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_);  		manager_->setAvatarManager(avatarManager_);  	} @@ -488,6 +489,7 @@ private:  	WhiteboardManager* wbManager_;  	HighlightManager* highlightManager_;  	ClientBlockListManager* clientBlockListManager_; +	std::map<std::string, std::string>* emoticons_;  };  CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest); diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp index f1fcf79..287fbd3 100644 --- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp @@ -1,5 +1,5 @@  /* - * 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.   */ @@ -67,7 +67,8 @@ public:  		highlightManager_ = new HighlightManager(settings_);  		muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_);  		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_, NULL, NULL, mucRegistry_, highlightManager_); +		emoticons_ = new std::map<std::string, std::string>(); +		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, emoticons_);  	}  	void tearDown() { @@ -86,6 +87,7 @@ public:  		delete iqChannel_;  		delete mucRegistry_;  		delete avatarManager_; +		delete emoticons_;  	}  	void finishJoin() { @@ -345,6 +347,7 @@ private:  	DummyEntityCapsProvider* entityCapsProvider_;  	DummySettingsProvider* settings_;  	HighlightManager* highlightManager_; +	std::map<std::string, std::string>* emoticons_;  };  CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest); diff --git a/Swift/Controllers/HighlightRule.cpp b/Swift/Controllers/HighlightRule.cpp index 28f26cf..9ca7d86 100644 --- a/Swift/Controllers/HighlightRule.cpp +++ b/Swift/Controllers/HighlightRule.cpp @@ -9,6 +9,7 @@  #include <boost/lambda/lambda.hpp>  #include <Swiften/Base/foreach.h> +#include <Swiften/Base/Regex.h>  #include <Swift/Controllers/HighlightRule.h>  namespace Swift { @@ -24,16 +25,7 @@ HighlightRule::HighlightRule()  boost::regex HighlightRule::regexFromString(const std::string & s) const  { -	// escape regex special characters: ^.$| etc -	// these need to be escaped: [\^\$\|.........] -	// and then C++ requires '\' to be escaped, too.... -	static const boost::regex esc("([\\^\\.\\$\\|\\(\\)\\[\\]\\*\\+\\?\\/\\{\\}\\\\])"); -	// matched character should be prepended with '\' -	// replace matched special character with \\\1 -	// and escape once more for C++ rules... -	static const std::string rep("\\\\\\1"); -	std::string escaped = boost::regex_replace(s , esc, rep); - +	std::string escaped = Regex::escape(s);  	std::string word = matchWholeWords_ ? "\\b" : "";  	boost::regex::flag_type flags = boost::regex::normal;  	if (!matchCase_) { diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index a661c81..f99e0ad 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -1,5 +1,5 @@  /* - * 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.   */ @@ -12,83 +12,87 @@  #include <Swift/Controllers/MainController.h> +#include <stdlib.h> +  #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/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 "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/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/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 <Swiften/Client/StanzaChannel.h>  #include <Swift/Controllers/HighlightManager.h>  #include <Swift/Controllers/HighlightEditorController.h> -#include <Swiften/Client/ClientBlockListManager.h>  #include <Swift/Controllers/BlockListController.h> -#include <Swiften/Crypto/CryptoProvider.h> +  namespace Swift { @@ -109,6 +113,7 @@ MainController::MainController(  		Notifier* notifier,  		URIHandler* uriHandler,  		IdleDetector* idleDetector, +		const std::map<std::string, std::string>& emoticons,  		bool useDelayForLatency) :  			eventLoop_(eventLoop),  			networkFactories_(networkFactories), @@ -120,7 +125,8 @@ MainController::MainController(  			idleDetector_(idleDetector),  			loginWindow_(NULL) ,  			useDelayForLatency_(useDelayForLatency), -			ftOverview_(NULL) { +			ftOverview_(NULL), +			emoticons_(emoticons) {  	storages_ = NULL;  	certificateStorage_ = NULL;  	statusTracker_ = NULL; @@ -339,9 +345,9 @@ void MainController::handleConnected() {  #ifdef SWIFT_EXPERIMENTAL_HISTORY  		historyController_ = new HistoryController(storages_->getHistoryStorage());  		historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_); -		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager()); +		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_);  #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()); +		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_);  #endif  		client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index d60805a..ba132e7 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -1,29 +1,33 @@  /* - * 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.   */  #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 {  	class IdleDetector; @@ -91,6 +95,7 @@ namespace Swift {  					Notifier* notifier,  					URIHandler* uriHandler,  					IdleDetector* idleDetector, +					const std::map<std::string, std::string>& emoticons,  					bool useDelayForLatency);  			~MainController(); @@ -184,5 +189,6 @@ namespace Swift {  			WhiteboardManager* whiteboardManager_;  			HighlightManager* highlightManager_;  			HighlightEditorController* highlightEditorController_; +			std::map<std::string, std::string> emoticons_;  	};  } diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h index a845cbe..dce852a 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -6,13 +6,14 @@  #pragma once +#include <vector> +#include <string> +  #include <boost/optional.hpp> -#include <Swiften/Base/boost_bsignals.h>  #include <boost/shared_ptr.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> @@ -30,8 +31,57 @@ namespace Swift {  	class FileTransferController;  	class InviteToChatWindow; +  	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 Tristate {Yes, No, Maybe}; @@ -48,18 +98,18 @@ namespace Swift {  			/** 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, const HighlightAction& highlight) = 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, const HighlightAction& highlight) = 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 std::string& message, Direction direction) = 0; -			virtual void addPresenceMessage(const std::string& message, Direction direction) = 0; +			virtual void addSystemMessage(const ChatMessage& message, Direction direction) = 0; +			virtual void addPresenceMessage(const ChatMessage& message, Direction direction) = 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, const HighlightAction& highlight) = 0; -			virtual void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 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  			virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) = 0; @@ -88,7 +138,7 @@ namespace Swift {  			virtual void setInputEnabled(bool enabled) = 0;  			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) = 0;  			virtual void setAckState(const std::string& id, AckState state) = 0;  			virtual void flash() = 0;  			virtual void setSubject(const std::string& subject) = 0; diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h index 4eb3bb6..fbfea93 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -6,7 +6,11 @@  #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 { @@ -14,11 +18,18 @@ namespace Swift {  			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&, const HighlightAction&) {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&, const HighlightAction&) {lastMessageBody_ = message; return "";} -			virtual void addSystemMessage(const std::string& /*message*/, Direction) {} -			virtual void addErrorMessage(const std::string& /*message*/) {} -			virtual void addPresenceMessage(const std::string& /*message*/, Direction) {} +			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*/) {};  			// 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; } @@ -40,9 +51,7 @@ namespace Swift {  			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&, const HighlightAction&) {} -			virtual void replaceWithAction(const std::string& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction&) {} +  			void setAckState(const std::string& /*id*/, AckState /*state*/) {}  			virtual void flash() {}  			virtual void setAlert(const std::string& /*alertText*/, const std::string& /*buttonText*/) {} @@ -62,6 +71,16 @@ namespace Swift {  			virtual void setBlockingState(BlockingState) {} +			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_;  			std::string lastMessageBody_;  			std::vector<SecurityLabelsCatalog::Item> labels_; diff --git a/Swift/QtUI/ChatSnippet.cpp b/Swift/QtUI/ChatSnippet.cpp index 666dd00..3436531 100644 --- a/Swift/QtUI/ChatSnippet.cpp +++ b/Swift/QtUI/ChatSnippet.cpp @@ -4,9 +4,10 @@   * See Documentation/Licenses/GPLv3.txt for more information.   */ +#include <Swift/QtUI/ChatSnippet.h> +  #include <QFile> -#include "ChatSnippet.h"  #include <Swift/QtUI/QtSwiftUtil.h>  namespace Swift { @@ -44,6 +45,18 @@ QString ChatSnippet::directionToCSS(Direction direction) {  	return direction == RTL ? QString("rtl") : QString("ltr");  } +ChatSnippet::Direction ChatSnippet::getDirection(const ChatWindow::ChatMessage& message) { +	boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; +	std::string text = ""; +	foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, message.getParts()) { +		if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { +			text = textPart->text; +			break; +		} +	} +	return getDirection(text); +} +  ChatSnippet::Direction ChatSnippet::getDirection(const std::string& message) {  	return getDirection(P2QSTRING(message));  } diff --git a/Swift/QtUI/ChatSnippet.h b/Swift/QtUI/ChatSnippet.h index d43947f..f60d486 100644 --- a/Swift/QtUI/ChatSnippet.h +++ b/Swift/QtUI/ChatSnippet.h @@ -10,7 +10,11 @@  #include <QString>  #include <QDateTime> -#include "QtChatTheme.h" + +#include <Swiften/Base/foreach.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/QtUI/QtChatTheme.h> +  namespace Swift {  	class ChatSnippet { @@ -48,6 +52,7 @@ namespace Swift {  			static QString timeToEscapedString(const QDateTime& time);  			static Direction getDirection(const std::string& message); +			static Direction getDirection(const ChatWindow::ChatMessage& message);  			static Direction getDirection(const QString& message);  		protected: diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp index 18c6e91..2536d39 100644 --- a/Swift/QtUI/QtChatView.cpp +++ b/Swift/QtUI/QtChatView.cpp @@ -128,7 +128,7 @@ void QtChatView::addMessageTop(boost::shared_ptr<ChatSnippet> snippet) {  		double size = 1.0 + 0.2 * fontSizeSteps_;  		QString sizeString(QString().setNum(size, 'g', 3) + "em");  		const QWebElementCollection spans = firstElement_.findAll("span.swift_resizable"); -		foreach (QWebElement span, spans) { +		Q_FOREACH (QWebElement span, spans) {  			span.setStyleProperty("font-size", sizeString);  		}  	} @@ -161,7 +161,7 @@ void QtChatView::addToDOM(boost::shared_ptr<ChatSnippet> snippet) {  		double size = 1.0 + 0.2 * fontSizeSteps_;  		QString sizeString(QString().setNum(size, 'g', 3) + "em");  		const QWebElementCollection spans = lastElement_.findAll("span.swift_resizable"); -		foreach (QWebElement span, spans) { +		Q_FOREACH (QWebElement span, spans) {  			span.setStyleProperty("font-size", sizeString);  		}  	} @@ -229,13 +229,13 @@ void QtChatView::replaceMessage(const QString& newMessage, const QString& id, co  void QtChatView::showEmoticons(bool show) {  	{  		const QWebElementCollection spans = document_.findAll("span.swift_emoticon_image"); -		foreach (QWebElement span, spans) { +		Q_FOREACH (QWebElement span, spans) {  			span.setStyleProperty("display", show ? "inline" : "none");  		}  	}  	{  		const QWebElementCollection spans = document_.findAll("span.swift_emoticon_text"); -		foreach (QWebElement span, spans) { +		Q_FOREACH (QWebElement span, spans) {  			span.setStyleProperty("display", show ? "none" : "inline");  		}  	} @@ -324,7 +324,7 @@ void QtChatView::resizeFont(int fontSizeSteps) {  	QString sizeString(QString().setNum(size, 'g', 3) + "em");  	//qDebug() << "Setting to " << sizeString;  	const QWebElementCollection spans = document_.findAll("span.swift_resizable"); -	foreach (QWebElement span, spans) { +	Q_FOREACH (QWebElement span, spans) {  		span.setStyleProperty("font-size", sizeString);  	}  	webView_->setFontSizeIsMinimal(size == 1.0); @@ -370,7 +370,7 @@ void QtChatView::resetView() {  static QWebElement findElementWithID(QWebElement document, QString elementName, QString id) {  	QWebElementCollection elements = document.findAll(elementName); -	foreach(QWebElement element, elements) { +	Q_FOREACH(QWebElement element, elements) {  		if (element.attribute("id") == id) {  			return element;  		} diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index 6dde1cc..d81de61 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -68,7 +68,7 @@ const QString QtChatWindow::ButtonFileTransferAcceptRequest = QString("filetrans  const QString QtChatWindow::ButtonMUCInvite = QString("mucinvite"); -QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), emoticons_(emoticons), blockingState_(BlockingUnsupported) { +QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), blockingState_(BlockingUnsupported) {  	settings_ = settings;  	unreadCount_ = 0;  	idCounter_ = 0; @@ -222,7 +222,7 @@ bool QtChatWindow::appendToPreviousCheck(QtChatWindow::PreviousMessageKind messa  	return previousMessageKind_ == messageKind && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_&& previousSenderName_ == P2QSTRING(senderName)));  } -ChatSnippet::Direction QtChatWindow::getActualDirection(const std::string& message, Direction direction) { +ChatSnippet::Direction QtChatWindow::getActualDirection(const ChatMessage& message, Direction direction) {  	if (direction == DefaultDirection) {  		return QCoreApplication::translate("QApplication", "QT_LAYOUT_DIRECTION") == "RTL" ? ChatSnippet::RTL : ChatSnippet::LTR;  	} @@ -497,17 +497,51 @@ void QtChatWindow::updateTitleWithUnreadCount() {  }  std::string QtChatWindow::addMessage( -		const std::string& message,  +		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 addMessage(linkimoticonify(message), senderName, senderIsSelf, label, avatarPath, "", time, highlight, ChatSnippet::getDirection(message)); +	return addMessage(chatMessageToHTML(message), senderName, senderIsSelf, label, avatarPath, "", time, highlight, ChatSnippet::getDirection(message)); +} + +QString QtChatWindow::chatMessageToHTML(const ChatMessage& message) { +	QString result; +	foreach (boost::shared_ptr<ChatMessagePart> part, message.getParts()) { +		boost::shared_ptr<ChatTextMessagePart> textPart; +		boost::shared_ptr<ChatURIMessagePart> uriPart; +		boost::shared_ptr<ChatEmoticonMessagePart> emoticonPart; +		boost::shared_ptr<ChatHighlightingMessagePart> highlightPart; + +		if ((textPart = boost::dynamic_pointer_cast<ChatTextMessagePart>(part))) { +			QString text = QtUtilities::htmlEscape(P2QSTRING(textPart->text)); +			text.replace("\n","<br/>"); +			result += text; +			continue; +		} +		if ((uriPart = boost::dynamic_pointer_cast<ChatURIMessagePart>(part))) { +			QString uri = QtUtilities::htmlEscape(P2QSTRING(uriPart->target)); +			result += "<a href='" + uri + "' >" + uri + "</a>"; +			continue; +		} +		if ((emoticonPart = boost::dynamic_pointer_cast<ChatEmoticonMessagePart>(part))) { +			QString textStyle = showEmoticons_ ? "style='display:none'" : ""; +			QString imageStyle = showEmoticons_ ? "" : "style='display:none'"; +			result += "<span class='swift_emoticon_image' " + imageStyle + "><img src='" + P2QSTRING(emoticonPart->imagePath) + "'/></span><span class='swift_emoticon_text' " + textStyle + ">" + QtUtilities::htmlEscape(P2QSTRING(emoticonPart->alternativeText)) + "</span>"; +			continue; +		} +		if ((highlightPart = boost::dynamic_pointer_cast<ChatHighlightingMessagePart>(part))) { +			//FIXME: Maybe do something here. Anything, really. +			continue; +		} + +	} +	return result;  } -QString QtChatWindow::linkimoticonify(const std::string& message) const { +/*QString QtChatWindow::linkimoticonify(const std::string& message) const {  	return linkimoticonify(P2QSTRING(message));  } @@ -515,18 +549,17 @@ QString QtChatWindow::linkimoticonify(const QString& message) const {  	QString messageHTML(message);  	messageHTML = QtUtilities::htmlEscape(messageHTML);  	QMapIterator<QString, QString> it(emoticons_); -	QString textStyle = showEmoticons_ ? "style='display:none'" : ""; -	QString imageStyle = showEmoticons_ ? "" : "style='display:none'"; +	  	if (messageHTML.length() < 500) {  		while (it.hasNext()) {  			it.next(); -			messageHTML.replace(it.key(), "<span class='swift_emoticon_image' " + imageStyle + "><img src='" + it.value() + "'/></span><span class='swift_emoticon_text' " + textStyle + ">"+it.key() + "</span>"); +			messageHTML.replace(it.key(), );  		}  		messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML)));  	}  	messageHTML.replace("\n","<br/>");  	return messageHTML; -} +}*/  QString QtChatWindow::getHighlightSpanStart(const HighlightAction& highlight) {  	QString color = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextColor())); @@ -562,12 +595,12 @@ std::string QtChatWindow::addMessage(  		htmlString = QString("<span style=\"border: thin dashed grey; padding-left: .5em; padding-right: .5em; color: %1; background-color: %2; font-size: 90%; margin-right: .5em; \" class='swift_label'>").arg(QtUtilities::htmlEscape(P2QSTRING(label->getForegroundColor()))).arg(QtUtilities::htmlEscape(P2QSTRING(label->getBackgroundColor())));  		htmlString += QString("%1</span> ").arg(QtUtilities::htmlEscape(P2QSTRING(label->getDisplayMarking())));  	} -	QString messageHTML(message); +  	QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">";  	QString styleSpanEnd = style == "" ? "" : "</span>";  	QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : "";  	QString highlightSpanEnd = highlight.highlightText() ? "</span>" : ""; -	htmlString += "<span class='swift_inner_message'>" + styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd + "</span>" ; +	htmlString += "<span class='swift_inner_message'>" + styleSpanStart + highlightSpanStart + message + highlightSpanEnd + styleSpanEnd + "</span>" ;  	bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf);  	if (lastLineTracker_.getShouldMoveLastLine()) { @@ -623,8 +656,8 @@ int QtChatWindow::getCount() {  	return unreadCount_;  } -std::string QtChatWindow::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, const HighlightAction& highlight) { -	return addMessage(" *" + linkimoticonify(message) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time, highlight, ChatSnippet::getDirection(message)); +std::string QtChatWindow::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 addMessage(" *" + chatMessageToHTML(message) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time, highlight, ChatSnippet::getDirection(message));  }  // FIXME: Move this to a different file @@ -802,36 +835,36 @@ void QtChatWindow::handleHTMLButtonClicked(QString id, QString encodedArgument1,  	}  } -void QtChatWindow::addErrorMessage(const std::string& errorMessage) { +void QtChatWindow::addErrorMessage(const ChatMessage& errorMessage) {  	if (isWidgetSelected()) {  		onAllMessagesRead();  	} -	QString errorMessageHTML(linkimoticonify(errorMessage)); -	errorMessageHTML.replace("\n","<br/>"); +	QString errorMessageHTML(chatMessageToHTML(errorMessage)); +	  	messageLog_->addMessageBottom(boost::make_shared<SystemMessageSnippet>("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_, ChatSnippet::getDirection(errorMessage)));  	previousMessageWasSelf_ = false;  	previousMessageKind_ = PreviousMessageWasSystem;  } -void QtChatWindow::addSystemMessage(const std::string& message, Direction direction) { +void QtChatWindow::addSystemMessage(const ChatMessage& message, Direction direction) {  	if (isWidgetSelected()) {  		onAllMessagesRead();  	} -	QString messageHTML = linkimoticonify(message); +	QString messageHTML = chatMessageToHTML(message);  	messageLog_->addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction)));  	previousMessageKind_ = PreviousMessageWasSystem;  } -void QtChatWindow::replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) { -	replaceMessage(" *" + linkimoticonify(message) + "*", id, time, "font-style:italic ", highlight); +void QtChatWindow::replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) { +	replaceMessage(" *" + chatMessageToHTML(message) + "*", id, time, "font-style:italic ", highlight);  } -void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) { -	replaceMessage(linkimoticonify(message), id, time, "", highlight); +void QtChatWindow::replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) { +	replaceMessage(chatMessageToHTML(message), id, time, "", highlight);  }  void QtChatWindow::replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style, const HighlightAction& highlight) { @@ -855,12 +888,12 @@ void QtChatWindow::replaceMessage(const QString& message, const std::string& id,  	}  } -void QtChatWindow::addPresenceMessage(const std::string& message, Direction direction) { +void QtChatWindow::addPresenceMessage(const ChatMessage& message, Direction direction) {  	if (isWidgetSelected()) {  		onAllMessagesRead();  	} -	QString messageHTML = linkimoticonify(message); +	QString messageHTML = chatMessageToHTML(message);  	messageLog_->addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction)));  	previousMessageKind_ = PreviousMessageWasPresence; @@ -932,13 +965,15 @@ void QtChatWindow::dropEvent(QDropEvent *event) {  	if (event->mimeData()->urls().size() == 1) {  		onSendFileRequest(Q2PSTRING(event->mimeData()->urls().at(0).toLocalFile()));  	} else { -		std::string message(Q2PSTRING(tr("Sending of multiple files at once isn't supported at this time."))); +		std::string messageText(Q2PSTRING(tr("Sending of multiple files at once isn't supported at this time."))); +		ChatMessage message; +		message.append(boost::make_shared<ChatTextMessagePart>(messageText));  		addSystemMessage(message, DefaultDirection);  	}  } -void QtChatWindow::replaceLastMessage(const std::string& message) { -	messageLog_->replaceLastMessage(linkimoticonify(message)); +void QtChatWindow::replaceLastMessage(const ChatMessage& message) { +	messageLog_->replaceLastMessage(chatMessageToHTML(message));  }  void QtChatWindow::setAvailableOccupantActions(const std::vector<OccupantAction>& actions) { @@ -1066,12 +1101,12 @@ void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& ji  	if (!direct) {  		htmlString += QObject::tr("This person may not have really sent this invitation!") + "\n";  	} -	htmlString = linkimoticonify(htmlString); +	htmlString = chatMessageToHTML(ChatMessage(Q2PSTRING(htmlString)));  	QString id = QString(ButtonMUCInvite + "%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));  	htmlString += "<div id='" + id + "'>" + -			buildChatWindowButton(linkimoticonify(tr("Accept Invite")), ButtonMUCInvite, QtUtilities::htmlEscape(P2QSTRING(jid.toString())), QtUtilities::htmlEscape(P2QSTRING(password)), id) + +			buildChatWindowButton(chatMessageToHTML(ChatMessage(Q2PSTRING((tr("Accept Invite"))))), ButtonMUCInvite, QtUtilities::htmlEscape(P2QSTRING(jid.toString())), QtUtilities::htmlEscape(P2QSTRING(password)), id) +  		"</div>";  	bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMUCInvite, senderName, false); diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h index a9600d4..a29edad 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -87,17 +87,17 @@ namespace Swift {  			static const QString ButtonMUCInvite;  		public: -			QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons); +			QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings);  			~QtChatWindow(); -			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, const HighlightAction& highlight); -			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, const HighlightAction& highlight); +			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); +			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); -			void addSystemMessage(const std::string& message, Direction direction); -			void addPresenceMessage(const std::string& message, Direction direction); -			void addErrorMessage(const std::string& errorMessage); +			void addSystemMessage(const ChatMessage& message, Direction direction); +			void addPresenceMessage(const ChatMessage& message, Direction direction); +			void addErrorMessage(const ChatMessage& message); -			void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight); -			void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight); +			void replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight); +			void replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight);  			// File transfer related stuff  			std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes);  			void setFileTransferProgress(std::string id, const int percentageDone); @@ -122,7 +122,7 @@ namespace Swift {  			void setRosterModel(Roster* roster);  			void setTabComplete(TabComplete* completer);  			int getCount(); -			void replaceLastMessage(const std::string& message); +			void replaceLastMessage(const ChatMessage& message);  			void setAckState(const std::string& id, AckState state);  			// message receipts @@ -214,9 +214,8 @@ namespace Swift {  					const HighlightAction& highlight);  			void handleOccupantSelectionChanged(RosterItem* item);  			bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) const; -			static ChatSnippet::Direction getActualDirection(const std::string& message, Direction direction); -			QString linkimoticonify(const std::string& message) const; -			QString linkimoticonify(const QString& message) const; +			static ChatSnippet::Direction getActualDirection(const ChatMessage& message, Direction direction); +			QString chatMessageToHTML(const ChatMessage& message);  			QString getHighlightSpanStart(const HighlightAction& highlight);  			int unreadCount_; @@ -256,7 +255,6 @@ namespace Swift {  			int idCounter_;  			SettingsProvider* settings_;  			std::vector<ChatWindow::RoomAction> availableRoomActions_; -			QMap<QString, QString> emoticons_;  			bool showEmoticons_;  			QPalette defaultLabelsPalette_;  			LabelModel* labelModel_; diff --git a/Swift/QtUI/QtChatWindowFactory.cpp b/Swift/QtUI/QtChatWindowFactory.cpp index bc5d7f4..78c04c9 100644 --- a/Swift/QtUI/QtChatWindowFactory.cpp +++ b/Swift/QtUI/QtChatWindowFactory.cpp @@ -21,7 +21,7 @@ namespace Swift {  static const QString SPLITTER_STATE = "mucSplitterState";  static const QString CHAT_TABS_GEOMETRY = "chatTabsGeometry"; -QtChatWindowFactory::QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath, QMap<QString, QString> emoticons) : themePath_(themePath), emoticons_(emoticons) { +QtChatWindowFactory::QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath) : themePath_(themePath) {  	qtOnlySettings_ = qtSettings;  	settings_ = settings;  	tabs_ = tabs; @@ -50,7 +50,7 @@ ChatWindow* QtChatWindowFactory::createChatWindow(const JID &contact,UIEventStre  		}  	} -	QtChatWindow *chatWindow = new QtChatWindow(P2QSTRING(contact.toString()), theme_, eventStream, settings_, emoticons_); +	QtChatWindow *chatWindow = new QtChatWindow(P2QSTRING(contact.toString()), theme_, eventStream, settings_);  	connect(chatWindow, SIGNAL(splitterMoved()), this, SLOT(handleSplitterMoved()));  	connect(this, SIGNAL(changeSplitterState(QByteArray)), chatWindow, SLOT(handleChangeSplitterState(QByteArray))); diff --git a/Swift/QtUI/QtChatWindowFactory.h b/Swift/QtUI/QtChatWindowFactory.h index 89a322f..63da514 100644 --- a/Swift/QtUI/QtChatWindowFactory.h +++ b/Swift/QtUI/QtChatWindowFactory.h @@ -23,7 +23,7 @@ namespace Swift {  	class QtChatWindowFactory : public QObject, public ChatWindowFactory {  		Q_OBJECT  		public: -			QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath, QMap<QString, QString> emoticons); +			QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath);  			~QtChatWindowFactory();  			ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream);  		signals: @@ -37,7 +37,6 @@ namespace Swift {  			QtSettingsProvider* qtOnlySettings_;  			QtChatTabs* tabs_;  			QtChatTheme* theme_; -			QMap<QString, QString> emoticons_;  	};  } diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp index b563c53..4d4cef9 100644 --- a/Swift/QtUI/QtSwift.cpp +++ b/Swift/QtUI/QtSwift.cpp @@ -4,65 +4,70 @@   * See Documentation/Licenses/GPLv3.txt for more information.   */ -#include "QtSwift.h" +#include <Swift/QtUI/QtSwift.h>  #include <string> -#include <QFile> +#include <map> +  #include <boost/bind.hpp> + +#include <QFile>  #include <QMessageBox>  #include <QApplication>  #include <QMap>  #include <qdebug.h> -#include <QtLoginWindow.h> -#include <QtChatTabs.h> -#include <QtSystemTray.h> -#include <QtSoundPlayer.h> -#include <QtSwiftUtil.h> -#include <QtUIFactory.h> -#include <QtChatWindowFactory.h> -#include <QtSingleWindow.h>  #include <Swiften/Base/Log.h>  #include <Swiften/Base/Path.h> -#include <Swift/Controllers/Storages/CertificateFileStorageFactory.h> -#include <Swift/Controllers/Storages/FileStoragesFactory.h> -#include <SwifTools/Application/PlatformApplicationPathProvider.h> -#include <string>  #include <Swiften/Base/Platform.h>  #include <Swiften/Elements/Presence.h>  #include <Swiften/Client/Client.h> +#include <Swiften/Base/Paths.h> + +#include <SwifTools/Application/PlatformApplicationPathProvider.h> +#include <SwifTools/AutoUpdater/AutoUpdater.h> +#include <SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h> + +#include <Swift/Controllers/Storages/CertificateFileStorageFactory.h> +#include <Swift/Controllers/Storages/FileStoragesFactory.h>  #include <Swift/Controllers/Settings/XMLSettingsProvider.h>  #include <Swift/Controllers/Settings/SettingsProviderHierachy.h>  #include <Swift/Controllers/MainController.h>  #include <Swift/Controllers/ApplicationInfo.h>  #include <Swift/Controllers/BuildVersion.h> -#include <SwifTools/AutoUpdater/AutoUpdater.h> -#include <SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h> -#include "Swiften/Base/Paths.h"  #include <Swift/Controllers/StatusCache.h> +#include <Swift/QtUI/QtLoginWindow.h> +#include <Swift/QtUI/QtChatTabs.h> +#include <Swift/QtUI/QtSystemTray.h> +#include <Swift/QtUI/QtSoundPlayer.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUIFactory.h> +#include <Swift/QtUI/QtChatWindowFactory.h> +#include <Swift/QtUI/QtSingleWindow.h> +  #if defined(SWIFTEN_PLATFORM_WINDOWS) -#include "WindowsNotifier.h" +#include <Swift/QtUI/WindowsNotifier.h>  #elif defined(HAVE_GROWL) -#include "SwifTools/Notifier/GrowlNotifier.h" +#include <SwifTools/Notifier/GrowlNotifier.h>  #elif defined(SWIFTEN_PLATFORM_LINUX) -#include "FreeDesktopNotifier.h" +#include <Swift/QtUI/FreeDesktopNotifier.h>  #else -#include "SwifTools/Notifier/NullNotifier.h" +#include <SwifTools/Notifier/NullNotifier.h>  #endif  #if defined(SWIFTEN_PLATFORM_MACOSX) -#include "SwifTools/Dock/MacOSXDock.h" +#include <SwifTools/Dock/MacOSXDock.h>  #else -#include "SwifTools/Dock/NullDock.h" +#include <SwifTools/Dock/NullDock.h>  #endif  #if defined(SWIFTEN_PLATFORM_MACOSX) -#include "QtURIHandler.h" +#include <Swift/QtUI/QtURIHandler.h>  #elif defined(SWIFTEN_PLATFORM_WIN32)  #include <SwifTools/URIHandler/NullURIHandler.h>  #else -#include "QtDBUSURIHandler.h" +#include <Swift/QtUI/QtDBUSURIHandler.h>  #endif  namespace Swift{ @@ -104,7 +109,7 @@ XMLSettingsProvider* QtSwift::loadSettingsFile(const QString& fileName) {  	return new XMLSettingsProvider("");  } -void QtSwift::loadEmoticonsFile(const QString& fileName, QMap<QString, QString>& emoticons)  { +void QtSwift::loadEmoticonsFile(const QString& fileName, std::map<std::string, std::string>& emoticons)  {  	QFile file(fileName);  	if (file.exists() && file.open(QIODevice::ReadOnly)) {  		while (!file.atEnd()) { @@ -117,7 +122,7 @@ void QtSwift::loadEmoticonsFile(const QString& fileName, QMap<QString, QString>&  				if (!emoticonFile.startsWith(":/") && !emoticonFile.startsWith("qrc:/")) {  					emoticonFile = "file://" + emoticonFile;  				} -				emoticons[tokens[0]] = emoticonFile; +				emoticons[Q2PSTRING(tokens[0])] = Q2PSTRING(emoticonFile);  			}  		}  	} @@ -135,7 +140,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa  	settingsHierachy_->addProviderToTopOfStack(xmlSettings_);  	settingsHierachy_->addProviderToTopOfStack(qtSettings_); -	QMap<QString, QString> emoticons; +	std::map<std::string, std::string> emoticons;  	loadEmoticonsFile(":/emoticons/emoticons.txt", emoticons);  	loadEmoticonsFile(P2QSTRING(pathToString(Paths::getExecutablePath() / "emoticons.txt")), emoticons); @@ -162,7 +167,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa  	applicationPathProvider_ = new PlatformApplicationPathProvider(SWIFT_APPLICATION_NAME);  	storagesFactory_ = new FileStoragesFactory(applicationPathProvider_->getDataDir(), networkFactories_.getCryptoProvider());  	certificateStorageFactory_ = new CertificateFileStorageFactory(applicationPathProvider_->getDataDir(), tlsFactories_.getCertificateFactory(), networkFactories_.getCryptoProvider()); -	chatWindowFactory_ = new QtChatWindowFactory(splitter_, settingsHierachy_, qtSettings_, tabs_, "", emoticons); +	chatWindowFactory_ = new QtChatWindowFactory(splitter_, settingsHierachy_, qtSettings_, tabs_, "");  	soundPlayer_ = new QtSoundPlayer(applicationPathProvider_);  	// Ugly, because the dock depends on the tray, but the temporary @@ -220,6 +225,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa  				notifier_,  				uriHandler_,  				&idleDetector_, +				emoticons,  				options.count("latency-debug") > 0);  		mainControllers_.push_back(mainController);  	} diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h index 216527d..1ea8886 100644 --- a/Swift/QtUI/QtSwift.h +++ b/Swift/QtUI/QtSwift.h @@ -61,7 +61,7 @@ namespace Swift {  			~QtSwift();  		private:  			XMLSettingsProvider* loadSettingsFile(const QString& fileName); -			void loadEmoticonsFile(const QString& fileName, QMap<QString, QString>& emoticons); +			void loadEmoticonsFile(const QString& fileName, std::map<std::string, std::string>& emoticons);  		private:  			QtEventLoop clientMainThreadCaller_;  			PlatformTLSFactories tlsFactories_; diff --git a/Swiften/Base/Regex.cpp b/Swiften/Base/Regex.cpp new file mode 100644 index 0000000..5e3d89a --- /dev/null +++ b/Swiften/Base/Regex.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Base/Regex.h> + +#include <string> + +#include <boost/regex.hpp> + +namespace Swift { + +	namespace Regex { +			std::string escape(const std::string& source) { +				// escape regex special characters: ^.$| etc +				// these need to be escaped: [\^\$\|.........] +				// and then C++ requires '\' to be escaped, too.... +				static const boost::regex esc("([\\^\\.\\$\\|\\(\\)\\[\\]\\*\\+\\?\\/\\{\\}\\\\])"); +				// matched character should be prepended with '\' +				// replace matched special character with \\\1 +				// and escape once more for C++ rules... +				static const std::string rep("\\\\\\1"); +				return boost::regex_replace(source, esc, rep); +			} + +	} + +} +  diff --git a/Swiften/Base/Regex.h b/Swiften/Base/Regex.h new file mode 100644 index 0000000..6d12a60 --- /dev/null +++ b/Swiften/Base/Regex.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2013 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> + +#include <Swiften/Base/API.h> + +namespace Swift { + +	namespace Regex { +		SWIFTEN_API std::string escape(const std::string& source); +	} + +} diff --git a/Swiften/Base/SConscript b/Swiften/Base/SConscript index 4956fba..094059a 100644 --- a/Swiften/Base/SConscript +++ b/Swiften/Base/SConscript @@ -16,5 +16,6 @@ objects = swiften_env.SwiftenObject([  			"BoostRandomGenerator.cpp",  			"sleep.cpp",  			"URL.cpp", +			"Regex.cpp"  		])  swiften_env.Append(SWIFTEN_OBJECTS = [objects]) | 
 Swift
 Swift