From db698bbb6d8c7e878e2cb997e18e572f3646e11d Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Sat, 1 Jun 2013 09:26:46 +0100 Subject: Refactor chat messages so parsing of links/emoticons happens in controllers. Change-Id: I07256f23ffbb6520f5063bdfbed9111946c46746 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, size_t> Linkify::splitLink(const std::string& input) { + std::vector result; + std::pair, size_t> pair; + std::vector 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 #include 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, 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("*http://swift.im*"), + result); + } - CPPUNIT_ASSERT_EQUAL( - std::string("*http://swift.im*"), - result); + void checkResult(const std::string& testling, size_t expectedIndex, std::string expectedSplit[]) { + std::pair, 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 #include #include #include -#include #include #include #include #include #include #include -#include #include -#include -#include -#include #include #include #include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include #include #include #include #include #include #include -#include -#include -#include #include #include -#include -#include + 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* 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 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* emoticons); virtual ~ChatController(); virtual void setToJID(const JID& jid); virtual void setAvailableServerFeatures(boost::shared_ptr 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 #include #include @@ -16,7 +16,6 @@ #include #include -#include #include #include #include @@ -25,19 +24,24 @@ #include #include #include -#include #include -#include -#include #include #include +#include + +#include + +#include +#include +#include +#include #include #include #include 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* 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 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 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())); - chatWindow_->addErrorMessage(errorMessage); + chatWindow_->addErrorMessage(parseMessageBody(errorMessage)); } } else if (messageEvent->getStanza()->getPayload()) { @@ -239,7 +243,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr 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 label = message->getPayload(); @@ -345,6 +349,91 @@ void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) { handleGeneralMUCInvitation(inviteEvent); } +typedef std::pair 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, 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(part)); + } + else { + parsedMessage.append(boost::make_shared(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 part, parsedMessage.getParts()) { + boost::shared_ptr textPart; + if ((textPart = boost::dynamic_pointer_cast(part))) { + try { + boost::match_results 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(std::string(start, matchStart))); + } + boost::shared_ptr emoticonPart = boost::make_shared(); + std::map::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(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 #include +#include + #include -#include "Swiften/Base/boost_bsignals.h" #include #include #include -#include "Swiften/Network/Timer.h" -#include "Swiften/Network/TimerFactory.h" -#include "Swiften/Elements/Stanza.h" -#include -#include "Swiften/Elements/DiscoInfo.h" -#include "Swift/Controllers/XMPPEvents/MessageEvent.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include -#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 -#include #include +#include 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* 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& 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 #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -25,25 +39,12 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include -#include -#include -#include #include #include -#include #include -#include namespace Swift { @@ -77,7 +78,8 @@ ChatsManager::ChatsManager( HistoryController* historyController, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, - ClientBlockListManager* clientBlockListManager) : + ClientBlockListManager* clientBlockListManager, + std::map* 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 +#include #include -#include #include #include #include #include #include +#include + #include #include #include -#include + 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* 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* 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* 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 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 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 MUCController::getMessageTimestamp(boo } void MUCController::updateJoinParts() { - chatWindow_->replaceLastMessage(generateJoinPartString(joinParts_)); + chatWindow_->replaceLastMessage(parseMessageBody(generateJoinPartString(joinParts_))); } void MUCController::appendToJoinParts(std::vector& 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 +#include +#include + #include -#include #include -#include -#include +#include #include -#include #include #include #include #include #include + +#include #include #include @@ -45,7 +48,7 @@ namespace Swift { class MUCController : public ChatControllerBase { public: - MUCController(const JID& self, MUC::ref muc, const boost::optional& 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& 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* emoticons); ~MUCController(); boost::signal onUserLeft; boost::signal 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(); + 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* 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(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); - controller_ = new MUCController (self_, muc_, boost::optional(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_); + emoticons_ = new std::map(); + controller_ = new MUCController (self_, muc_, boost::optional(), 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* 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 #include +#include #include 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 +#include + #include #include #include #include -#include -#include + #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + #include #include -#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include "Swift/Controllers/AdHocManager.h" -#include +#include #include -#include -#include #include -#include #include #include -#include #include -#include + namespace Swift { @@ -109,6 +113,7 @@ MainController::MainController( Notifier* notifier, URIHandler* uriHandler, IdleDetector* idleDetector, + const std::map& 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 -#include #include - -#include "Swiften/Network/Timer.h" +#include #include -#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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + namespace Swift { class IdleDetector; @@ -91,6 +95,7 @@ namespace Swift { Notifier* notifier, URIHandler* uriHandler, IdleDetector* idleDetector, + const std::map& emoticons, bool useDelayForLatency); ~MainController(); @@ -184,5 +189,6 @@ namespace Swift { WhiteboardManager* whiteboardManager_; HighlightManager* highlightManager_; HighlightEditorController* highlightEditorController_; + std::map 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 +#include + #include -#include #include #include -#include -#include +#include #include #include #include @@ -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(text)); + } + void append(const boost::shared_ptr& part) { + parts_.push_back(part); + } + + const std::vector >& getParts() const { + return parts_; + } + private: + std::vector > 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 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 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 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 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 + +#include +#include + 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 /*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 /*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 /*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 /*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 text; + foreach (boost::shared_ptr part, message.getParts()) { + if ((text = boost::dynamic_pointer_cast(part))) { + return text->text; + } + } + return ""; + } + std::string name_; std::string lastMessageBody_; std::vector 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 + #include -#include "ChatSnippet.h" #include 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 textPart; + std::string text = ""; + foreach (boost::shared_ptr part, message.getParts()) { + if ((textPart = boost::dynamic_pointer_cast(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 #include -#include "QtChatTheme.h" + +#include +#include +#include + 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 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 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 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 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 part, message.getParts()) { + boost::shared_ptr textPart; + boost::shared_ptr uriPart; + boost::shared_ptr emoticonPart; + boost::shared_ptr highlightPart; + + if ((textPart = boost::dynamic_pointer_cast(part))) { + QString text = QtUtilities::htmlEscape(P2QSTRING(textPart->text)); + text.replace("\n","
"); + result += text; + continue; + } + if ((uriPart = boost::dynamic_pointer_cast(part))) { + QString uri = QtUtilities::htmlEscape(P2QSTRING(uriPart->target)); + result += "" + uri + ""; + continue; + } + if ((emoticonPart = boost::dynamic_pointer_cast(part))) { + QString textStyle = showEmoticons_ ? "style='display:none'" : ""; + QString imageStyle = showEmoticons_ ? "" : "style='display:none'"; + result += "" + QtUtilities::htmlEscape(P2QSTRING(emoticonPart->alternativeText)) + ""; + continue; + } + if ((highlightPart = boost::dynamic_pointer_cast(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 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(), ""+it.key() + ""); + messageHTML.replace(it.key(), ); } messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML))); } messageHTML.replace("\n","
"); 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("").arg(QtUtilities::htmlEscape(P2QSTRING(label->getForegroundColor()))).arg(QtUtilities::htmlEscape(P2QSTRING(label->getBackgroundColor()))); htmlString += QString("%1 ").arg(QtUtilities::htmlEscape(P2QSTRING(label->getDisplayMarking()))); } - QString messageHTML(message); + QString styleSpanStart = style == "" ? "" : ""; QString styleSpanEnd = style == "" ? "" : ""; QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : ""; QString highlightSpanEnd = highlight.highlightText() ? "" : ""; - htmlString += "" + styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd + "" ; + htmlString += "" + styleSpanStart + highlightSpanStart + message + highlightSpanEnd + styleSpanEnd + "" ; 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 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 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","
"); + QString errorMessageHTML(chatMessageToHTML(errorMessage)); + messageLog_->addMessageBottom(boost::make_shared("" + errorMessageHTML + "", 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(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(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(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& 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(idCounter_++))); htmlString += "
" + - 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) + "
"; 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 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 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 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 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 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 availableRoomActions_; - QMap 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 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 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 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 #include -#include +#include + #include + +#include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include -#include -#include -#include #include #include #include +#include + +#include +#include +#include + +#include +#include #include #include #include #include #include -#include -#include -#include "Swiften/Base/Paths.h" #include +#include +#include +#include +#include +#include +#include +#include +#include + #if defined(SWIFTEN_PLATFORM_WINDOWS) -#include "WindowsNotifier.h" +#include #elif defined(HAVE_GROWL) -#include "SwifTools/Notifier/GrowlNotifier.h" +#include #elif defined(SWIFTEN_PLATFORM_LINUX) -#include "FreeDesktopNotifier.h" +#include #else -#include "SwifTools/Notifier/NullNotifier.h" +#include #endif #if defined(SWIFTEN_PLATFORM_MACOSX) -#include "SwifTools/Dock/MacOSXDock.h" +#include #else -#include "SwifTools/Dock/NullDock.h" +#include #endif #if defined(SWIFTEN_PLATFORM_MACOSX) -#include "QtURIHandler.h" +#include #elif defined(SWIFTEN_PLATFORM_WIN32) #include #else -#include "QtDBUSURIHandler.h" +#include #endif namespace Swift{ @@ -104,7 +109,7 @@ XMLSettingsProvider* QtSwift::loadSettingsFile(const QString& fileName) { return new XMLSettingsProvider(""); } -void QtSwift::loadEmoticonsFile(const QString& fileName, QMap& emoticons) { +void QtSwift::loadEmoticonsFile(const QString& fileName, std::map& 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& 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 emoticons; + std::map 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& emoticons); + void loadEmoticonsFile(const QString& fileName, std::map& 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 + +#include + +#include + +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 + +#include + +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]) -- cgit v0.10.2-6-g49f6