diff options
| author | Richard Maudsley <richard.maudsley@isode.com> | 2014-01-13 15:26:24 (GMT) |
|---|---|---|
| committer | Swift Review <review@swift.im> | 2014-07-09 14:01:41 (GMT) |
| commit | f2bcc401477dcb5ca52b5d9d5e85f4bf7bae9285 (patch) | |
| tree | 01cf807b2ad59f5ea6504fd28d12e0f994e2f907 /Swift | |
| parent | 8e03583fe21bcd5e0025da81d8f4a34ed05cd058 (diff) | |
| download | swift-contrib-f2bcc401477dcb5ca52b5d9d5e85f4bf7bae9285.zip swift-contrib-f2bcc401477dcb5ca52b5d9d5e85f4bf7bae9285.tar.bz2 | |
Reworked highlight rules dialog. Added support for highlighting individual words in messages.
Change-Id: I378fa69077c29008db4ef7c2265e5212924bc2ce
Diffstat (limited to 'Swift')
38 files changed, 1571 insertions, 675 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 2367761..9df7708 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -17,71 +17,71 @@ #include <Swiften/Base/format.h> #include <Swiften/Base/Log.h> #include <Swiften/Chat/ChatStateNotifier.h> #include <Swiften/Chat/ChatStateTracker.h> #include <Swiften/Client/ClientBlockListManager.h> #include <Swiften/Client/NickResolver.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Disco/EntityCapsProvider.h> #include <Swiften/Elements/DeliveryReceipt.h> #include <Swiften/Elements/DeliveryReceiptRequest.h> #include <Swiften/Elements/Idle.h> #include <Swiften/FileTransfer/FileTransferManager.h> #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <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 <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> #include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/Highlighter.h> #include <Swift/Controllers/Chat/ChatMessageParser.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, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) +ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) { isInMUC_ = isInMUC; lastWasPresence_ = false; chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider); chatStateTracker_ = new ChatStateTracker(); nickResolver_ = nickResolver; presenceOracle_->onPresenceChange.connect(boost::bind(&ChatController::handlePresenceChange, this, _1)); chatStateTracker_->onChatStateChange.connect(boost::bind(&ChatWindow::setContactChatState, chatWindow_, _1)); stanzaChannel_->onStanzaAcked.connect(boost::bind(&ChatController::handleStanzaAcked, this, _1)); nickResolver_->onNickChanged.connect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2)); std::string nick = nickResolver_->jidToNick(toJID_); chatWindow_->setName(nick); std::string startMessage; Presence::ref theirPresence; if (isInMUC) { startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% in chatroom %2%")) % nick % contact.toBare().toString()); theirPresence = presenceOracle->getLastPresence(contact); } else { startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% - %2%")) % nick % contact.toBare().toString()); theirPresence = contact.isBare() ? presenceOracle->getHighestPriorityPresence(contact.toBare()) : presenceOracle->getLastPresence(contact); } Idle::ref idle; if (theirPresence && (idle = theirPresence->getPayload<Idle>())) { startMessage += str(format(QT_TRANSLATE_NOOP("", ", who has been idle since %1%")) % dateTimeToLocalString(idle->getSince())); } startMessage += ": " + statusShowTypeToFriendlyName(theirPresence ? theirPresence->getShow() : StatusShow::None); if (theirPresence && !theirPresence->getStatus().empty()) { startMessage += " (" + theirPresence->getStatus() + ")"; } lastShownStatus_ = theirPresence ? theirPresence->getShow() : StatusShow::None; chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available); startMessage += "."; chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection); chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); @@ -286,71 +286,71 @@ void ChatController::handleBlockingItemRemoved(const JID& jid) { } void ChatController::handleBlockUserRequest() { if (isInMUC_) { eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_)); } else { eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_.toBare())); } } void ChatController::handleUnblockUserRequest() { if (isInMUC_) { eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_)); } else { eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_.toBare())); } } void ChatController::handleInviteToChat(const std::vector<JID>& droppedJIDs) { boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(toJID_.toBare(), droppedJIDs, RequestInviteToMUCUIEvent::Impromptu)); eventStream_->send(event); } void ChatController::handleUIEvent(boost::shared_ptr<UIEvent> event) { boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event); if (inviteEvent && inviteEvent->getRoom() == toJID_.toBare()) { onConvertToMUC(detachChatWindow(), inviteEvent->getInvites(), inviteEvent->getReason()); } } void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) { boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>(); if (replace) { eraseIf(unackedStanzas_, PairSecondEquals<boost::shared_ptr<Stanza>, std::string>(myLastMessageUIID_)); - replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time(), HighlightAction()); + replaceMessage(body, myLastMessageUIID_, true, boost::posix_time::microsec_clock::universal_time(), HighlightAction()); } else { myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), avatarManager_->getAvatarPath(selfJID_), boost::posix_time::microsec_clock::universal_time(), HighlightAction()); } if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) { chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending); unackedStanzas_[sentStanza] = myLastMessageUIID_; } if (sentStanza->getPayload<DeliveryReceiptRequest>()) { requestedReceipts_[sentStanza->getID()] = myLastMessageUIID_; chatWindow_->setMessageReceiptState(myLastMessageUIID_, ChatWindow::ReceiptRequested); } lastWasPresence_ = false; chatStateNotifier_->userSentMessage(); } void ChatController::handleStanzaAcked(boost::shared_ptr<Stanza> stanza) { std::map<boost::shared_ptr<Stanza>, std::string>::iterator unackedStanza = unackedStanzas_.find(stanza); if (unackedStanza != unackedStanzas_.end()) { chatWindow_->setAckState(unackedStanza->second, ChatWindow::Received); unackedStanzas_.erase(unackedStanza); } } void ChatController::setOnline(bool online) { if (!online) { std::map<boost::shared_ptr<Stanza>, std::string>::iterator it = unackedStanzas_.begin(); for ( ; it != unackedStanzas_.end(); ++it) { chatWindow_->setAckState(it->second, ChatWindow::Failed); } unackedStanzas_.clear(); Presence::ref fakeOffline(new Presence()); diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index f8b6d8b..8b1bb9a 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -1,66 +1,66 @@ /* - * Copyright (c) 2010-2013 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once #include <Swift/Controllers/Chat/ChatControllerBase.h> #include <map> #include <string> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { class AvatarManager; class ChatStateNotifier; class ChatStateTracker; class NickResolver; class EntityCapsProvider; class FileTransferController; class SettingsProvider; class HistoryController; class HighlightManager; class ClientBlockListManager; class UIEvent; class ChatController : public ChatControllerBase { public: - ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); + ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); virtual ~ChatController(); virtual void setToJID(const JID& jid); virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); virtual void setOnline(bool online); virtual void handleNewFileTransferController(FileTransferController* ftc); virtual void handleWhiteboardSessionRequest(bool senderIsSelf); virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state); virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/); virtual ChatWindow* detachChatWindow(); protected: void cancelReplaces(); JID getBaseJID(); void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming); private: void handlePresenceChange(boost::shared_ptr<Presence> newPresence); std::string getStatusChangeString(boost::shared_ptr<Presence> presence); bool isIncomingMessageFromMe(boost::shared_ptr<Message> message); void postSendMessage(const std::string &body, boost::shared_ptr<Stanza> sentStanza); void preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent); void postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction&); void preSendMessageRequest(boost::shared_ptr<Message>); std::string senderDisplayNameFromMessage(const JID& from); virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const; void handleStanzaAcked(boost::shared_ptr<Stanza> stanza); void dayTicked() {lastWasPresence_ = false;} void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/); void handleBareJIDCapsChanged(const JID& jid); void handleFileTransferCancel(std::string /* id */); void handleFileTransferStart(std::string /* id */, std::string /* description */); void handleFileTransferAccept(std::string /* id */, std::string /* filename */); void handleSendFileRequest(std::string filename); diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index 23137dc..5363e0c 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -1,80 +1,80 @@ /* - * Copyright (c) 2010-2013 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swift/Controllers/Chat/ChatControllerBase.h> #include <sstream> #include <map> #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> #include <boost/smart_ptr/make_shared.hpp> #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/numeric/conversion/cast.hpp> #include <boost/algorithm/string.hpp> #include <Swiften/Base/format.h> #include <Swiften/Base/Path.h> #include <Swiften/Base/String.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Elements/Delay.h> #include <Swiften/Elements/MUCInvitationPayload.h> #include <Swiften/Elements/MUCUserPayload.h> #include <Swiften/Base/foreach.h> #include <Swiften/Disco/EntityCapsProvider.h> #include <Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h> #include <Swiften/Avatars/AvatarManager.h> #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h> #include <Swift/Controllers/HighlightManager.h> #include <Swift/Controllers/Highlighter.h> #include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> namespace Swift { -ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) { +ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) { chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream); chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this)); chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2)); chatWindow_->onLogCleared.connect(boost::bind(&ChatControllerBase::handleLogCleared, this)); entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1)); highlighter_ = highlightManager->createHighlighter(); setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable()); createDayChangeTimer(); } ChatControllerBase::~ChatControllerBase() { delete chatWindow_; } void ChatControllerBase::handleLogCleared() { cancelReplaces(); } ChatWindow* ChatControllerBase::detachChatWindow() { ChatWindow* chatWindow = chatWindow_; chatWindow_ = NULL; return chatWindow; } void ChatControllerBase::handleCapsChanged(const JID& jid) { if (jid.compare(toJID_, JID::WithoutResource) == 0) { handleBareJIDCapsChanged(jid); } } void ChatControllerBase::setCanStartImpromptuChats(bool supportsImpromptu) { if (chatWindow_) { chatWindow_->setCanInitiateImpromptuChats(supportsImpromptu); } } @@ -169,150 +169,150 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool onActivity(message->getBody()); #ifdef SWIFT_EXPERIMENTAL_HISTORY logMessage(body, selfJID_, toJID_, now, false); #endif } void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) { if (catalog && !error) { if (catalog->getItems().size() == 0) { chatWindow_->setSecurityLabelsEnabled(false); labelsEnabled_ = false; } else { labelsEnabled_ = true; chatWindow_->setAvailableSecurityLabels(catalog->getItems()); chatWindow_->setSecurityLabelsEnabled(true); } } else { labelsEnabled_ = false; chatWindow_->setSecurityLabelsError(); } } void ChatControllerBase::showChatWindow() { chatWindow_->show(); } void ChatControllerBase::activateChatWindow() { chatWindow_->activate(); } 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(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight); } else { - return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight); + return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message,senderIsSelf), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight); } } -void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) { +void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, bool senderIsSelf, const boost::posix_time::ptime& time, const HighlightAction& highlight) { if (boost::starts_with(message, "/me ")) { chatWindow_->replaceWithAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), id, time, highlight); } else { - chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message), id, time, highlight); + chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message,senderIsSelf), id, time, highlight); } } bool ChatControllerBase::isFromContact(const JID& from) { return from.toBare() == toJID_.toBare(); } void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { preHandleIncomingMessage(messageEvent); if (messageEvent->isReadable() && !messageEvent->getConcluded()) { unreadMessages_.push_back(messageEvent); if (messageEvent->targetsMe()) { targetedUnreadMessages_.push_back(messageEvent); } } boost::shared_ptr<Message> message = messageEvent->getStanza(); std::string body = message->getBody(); HighlightAction highlight; if (message->isError()) { if (!message->getTo().getResource().empty()) { std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>())); chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); } } else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) { handleMUCInvitation(messageEvent->getStanza()); return; } else if (messageEvent->getStanza()->getPayload<MUCUserPayload>() && messageEvent->getStanza()->getPayload<MUCUserPayload>()->getInvite()) { handleMediatedMUCInvitation(messageEvent->getStanza()); return; } else { if (!messageEvent->isReadable()) { return; } showChatWindow(); JID from = message->getFrom(); std::vector<boost::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>(); for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) { if (!delayPayloads[i]->getFrom()) { continue; } 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(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection); } boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>(); // Determine the timestamp boost::posix_time::ptime timeStamp = boost::posix_time::microsec_clock::universal_time(); boost::optional<boost::posix_time::ptime> messageTimeStamp = getMessageTimestamp(message); if (messageTimeStamp) { timeStamp = *messageTimeStamp; } onActivity(body); // Highlight if (!isIncomingMessageFromMe(message)) { highlight = highlighter_->findAction(body, senderDisplayNameFromMessage(from)); } boost::shared_ptr<Replace> replace = message->getPayload<Replace>(); if (replace) { std::string body = message->getBody(); // Should check if the user has a previous message std::map<JID, std::string>::iterator lastMessage; lastMessage = lastMessagesUIID_.find(from); if (lastMessage != lastMessagesUIID_.end()) { - replaceMessage(body, lastMessagesUIID_[from], timeStamp, highlight); + replaceMessage(body, lastMessagesUIID_[from], isIncomingMessageFromMe(message), timeStamp, highlight); } } else { lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, avatarManager_->getAvatarPath(from), timeStamp, highlight); } logMessage(body, from, selfJID_, timeStamp, true); } chatWindow_->show(); chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size())); onUnreadCountChanged(); postHandleIncomingMessage(messageEvent, highlight); } std::string ChatControllerBase::getErrorMessage(boost::shared_ptr<ErrorPayload> error) { std::string defaultMessage = QT_TRANSLATE_NOOP("", "Error sending message"); if (!error->getText().empty()) { return error->getText(); } else { switch (error->getCondition()) { case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request"); case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict"); case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented"); case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden"); case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted"); case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error"); case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found"); case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed"); case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected"); case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed"); case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized"); case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required"); case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable"); case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect"); diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index 7db94a4..cf0a4d2 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -1,134 +1,134 @@ /* - * Copyright (c) 2010-2013 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once #include <map> #include <vector> #include <string> #include <boost/shared_ptr.hpp> #include <boost/filesystem/path.hpp> #include <boost/optional.hpp> #include <boost/date_time/posix_time/posix_time.hpp> #include <Swiften/Network/Timer.h> #include <Swiften/Network/TimerFactory.h> #include <Swiften/Elements/Stanza.h> #include <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 <Swift/Controllers/HistoryController.h> #include <Swift/Controllers/HighlightManager.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { class IQRouter; class StanzaChannel; class ChatWindowFactory; class AvatarManager; class UIEventStream; class EventController; class EntityCapsProvider; class HighlightManager; class Highlighter; class ChatMessageParser; class AutoAcceptMUCInviteDecider; class ChatControllerBase : public boost::bsignals::trackable { public: virtual ~ChatControllerBase(); void showChatWindow(); void activateChatWindow(); virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); void handleIncomingMessage(boost::shared_ptr<MessageEvent> message); std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight); - void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight); + void replaceMessage(const std::string& message, const std::string& id, bool senderIsSelf, const boost::posix_time::ptime& time, const HighlightAction& highlight); virtual void setOnline(bool online); virtual void setEnabled(bool enabled); virtual void setToJID(const JID& jid) {toJID_ = jid;} /** Used for determining when something is recent.*/ boost::signal<void (const std::string& /*activity*/)> onActivity; boost::signal<void ()> onUnreadCountChanged; int getUnreadCount(); const JID& getToJID() {return toJID_;} void handleCapsChanged(const JID& jid); void setCanStartImpromptuChats(bool supportsImpromptu); virtual ChatWindow* detachChatWindow(); boost::signal<void(ChatWindow* /*window to reuse*/, const std::vector<JID>& /*invite people*/, const std::string& /*reason*/)> onConvertToMUC; protected: - ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); + ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); /** * Pass the Message appended, and the stanza used to send it. */ virtual void postSendMessage(const std::string&, boost::shared_ptr<Stanza>) {} virtual std::string senderDisplayNameFromMessage(const JID& from) = 0; virtual bool isIncomingMessageFromMe(boost::shared_ptr<Message>) = 0; virtual void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {} virtual void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>, const HighlightAction&) {} virtual void preSendMessageRequest(boost::shared_ptr<Message>) {} virtual bool isFromContact(const JID& from); virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const = 0; virtual void dayTicked() {} virtual void handleBareJIDCapsChanged(const JID& jid) = 0; std::string getErrorMessage(boost::shared_ptr<ErrorPayload>); virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {} virtual void cancelReplaces() = 0; /** JID any iq for account should go to - bare except for PMs */ virtual JID getBaseJID(); virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0; private: IDGenerator idGenerator_; std::string lastSentMessageStanzaID_; void createDayChangeTimer(); void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage); void handleAllMessagesRead(); void handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error); void handleDayChangeTick(); void handleMUCInvitation(Message::ref message); void handleMediatedMUCInvitation(Message::ref message); void handleGeneralMUCInvitation(MUCInviteEvent::ref event); void handleLogCleared(); protected: JID selfJID_; std::vector<boost::shared_ptr<StanzaEvent> > unreadMessages_; std::vector<boost::shared_ptr<StanzaEvent> > targetedUnreadMessages_; StanzaChannel* stanzaChannel_; IQRouter* iqRouter_; ChatWindowFactory* chatWindowFactory_; ChatWindow* chatWindow_; JID toJID_; bool labelsEnabled_; std::map<JID, std::string> lastMessagesUIID_; PresenceOracle* presenceOracle_; AvatarManager* avatarManager_; bool useDelayForLatency_; EventController* eventController_; boost::shared_ptr<Timer> dateChangeTimer_; TimerFactory* timerFactory_; EntityCapsProvider* entityCapsProvider_; SecurityLabelsCatalog::Item lastLabel_; HistoryController* historyController_; MUCRegistry* mucRegistry_; Highlighter* highlighter_; - ChatMessageParser* chatMessageParser_; + boost::shared_ptr<ChatMessageParser> chatMessageParser_; AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; UIEventStream* eventStream_; }; } diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp index 698b766..09d93ac 100644 --- a/Swift/Controllers/Chat/ChatMessageParser.cpp +++ b/Swift/Controllers/Chat/ChatMessageParser.cpp @@ -1,90 +1,103 @@ /* * Copyright (c) 2013-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swift/Controllers/Chat/ChatMessageParser.h> #include <vector> #include <utility> #include <boost/smart_ptr/make_shared.hpp> #include <boost/algorithm/string.hpp> #include <Swiften/Base/Regex.h> #include <Swiften/Base/foreach.h> #include <SwifTools/Linkify.h> namespace Swift { - ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons) : emoticons_(emoticons) { - + ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode) + : emoticons_(emoticons), highlightRules_(highlightRules), mucMode_(mucMode) { } typedef std::pair<std::string, std::string> StringPair; - ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body) { + ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, bool senderIsSelf) { ChatWindow::ChatMessage parsedMessage; std::string remaining = body; /* Parse one, URLs */ while (!remaining.empty()) { bool found = false; std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining); remaining = ""; for (size_t i = 0; i < links.first.size(); i++) { const std::string& part = links.first[i]; if (found) { // Must be on the last part, then remaining = part; } else { if (i == links.second) { found = true; parsedMessage.append(boost::make_shared<ChatWindow::ChatURIMessagePart>(part)); } else { parsedMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(part)); } } } } - + /* do emoticon substitution */ + parsedMessage = emoticonHighlight(parsedMessage); + + if (!senderIsSelf) { /* do not highlight our own messsages */ + /* do word-based color highlighting */ + parsedMessage = splitHighlight(parsedMessage); + } + + return parsedMessage; + } + + ChatWindow::ChatMessage ChatMessageParser::emoticonHighlight(const ChatWindow::ChatMessage& message) + { + ChatWindow::ChatMessage parsedMessage = message; std::string regexString; /* Parse two, emoticons */ foreach (StringPair emoticon, emoticons_) { /* Construct a regexp that finds an instance of any of the emoticons inside a group * at the start or end of the line, or beside whitespace. */ regexString += regexString.empty() ? "" : "|"; std::string escaped = "(" + Regex::escape(emoticon.first) + ")"; regexString += "^" + escaped + "|"; regexString += escaped + "$|"; regexString += "\\s" + escaped + "|"; regexString += escaped + "\\s"; } if (!regexString.empty()) { regexString += ""; boost::regex emoticonRegex(regexString); ChatWindow::ChatMessage newMessage; foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) { boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { try { boost::match_results<std::string::const_iterator> match; const std::string& text = textPart->text; std::string::const_iterator start = text.begin(); while (regex_search(start, text.end(), match, emoticonRegex)) { int matchIndex = 0; for (matchIndex = 1; matchIndex < static_cast<int>(match.size()); matchIndex++) { if (match[matchIndex].length() > 0) { //This is the matching subgroup break; } } @@ -92,36 +105,90 @@ namespace Swift { std::string::const_iterator matchEnd = match[matchIndex].second; if (start != matchStart) { /* If we're skipping over plain text since the previous emoticon, record it as plain text */ newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); } boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = boost::make_shared<ChatWindow::ChatEmoticonMessagePart>(); std::string matchString = match[matchIndex].str(); std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(matchString); assert (emoticonIterator != emoticons_.end()); const StringPair& emoticon = *emoticonIterator; emoticonPart->imagePath = emoticon.second; emoticonPart->alternativeText = emoticon.first; newMessage.append(emoticonPart); start = matchEnd; } if (start != text.end()) { /* If there's plain text after the last emoticon, record it */ newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); } } catch (std::runtime_error) { /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ newMessage.append(part); } } else { newMessage.append(part); } } parsedMessage = newMessage; } return parsedMessage; } + + ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message) + { + ChatWindow::ChatMessage parsedMessage = message; + + for (size_t i = 0; i < highlightRules_->getSize(); ++i) { + const HighlightRule& rule = highlightRules_->getRule(i); + if (rule.getMatchMUC() && !mucMode_) { + continue; /* this rule only applies to MUC's, and this is a CHAT */ + } else if (rule.getMatchChat() && mucMode_) { + continue; /* this rule only applies to CHAT's, and this is a MUC */ + } + foreach(const boost::regex ®ex, rule.getKeywordRegex()) { + ChatWindow::ChatMessage newMessage; + foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) { + boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; + if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { + try { + boost::match_results<std::string::const_iterator> match; + const std::string& text = textPart->text; + std::string::const_iterator start = text.begin(); + while (regex_search(start, text.end(), match, regex)) { + std::string::const_iterator matchStart = match[0].first; + std::string::const_iterator matchEnd = match[0].second; + if (start != matchStart) { + /* If we're skipping over plain text since the previous emoticon, record it as plain text */ + newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); + } + boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = boost::make_shared<ChatWindow::ChatHighlightingMessagePart>(); + highlightPart->text = match.str(); + highlightPart->foregroundColor = rule.getAction().getTextColor(); + highlightPart->backgroundColor = rule.getAction().getTextBackground(); + newMessage.append(highlightPart); + start = matchEnd; + } + if (start != text.end()) { + /* If there's plain text after the last emoticon, record it */ + newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); + } + } + catch (std::runtime_error) { + /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ + newMessage.append(part); + } + } else { + newMessage.append(part); + } + } + parsedMessage = newMessage; + } + } + + return parsedMessage; + } } diff --git a/Swift/Controllers/Chat/ChatMessageParser.h b/Swift/Controllers/Chat/ChatMessageParser.h index c9b9456..cff4ffa 100644 --- a/Swift/Controllers/Chat/ChatMessageParser.h +++ b/Swift/Controllers/Chat/ChatMessageParser.h @@ -1,23 +1,26 @@ /* - * Copyright (c) 2013 Kevin Smith + * Copyright (c) 2013-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once #include <string> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { class ChatMessageParser { public: - ChatMessageParser(const std::map<std::string, std::string>& emoticons); - ChatWindow::ChatMessage parseMessageBody(const std::string& body); + ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode = false); + ChatWindow::ChatMessage parseMessageBody(const std::string& body, bool senderIsSelf = false); private: + ChatWindow::ChatMessage emoticonHighlight(const ChatWindow::ChatMessage& parsedMessage); + ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage); std::map<std::string, std::string> emoticons_; - + HighlightRulesListPtr highlightRules_; + bool mucMode_; }; } diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index 1698b4a..8a077d1 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -113,134 +113,133 @@ ChatsManager::ChatsManager( PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons, UserSearchController* inviteUserSearchController, VCardManager* vcardManager) : jid_(jid), joinMUCWindowFactory_(joinMUCWindowFactory), useDelayForLatency_(useDelayForLatency), mucRegistry_(mucRegistry), entityCapsProvider_(entityCapsProvider), mucManager(mucManager), ftOverview_(ftOverview), roster_(roster), eagleMode_(eagleMode), settings_(settings), historyController_(historyController), whiteboardManager_(whiteboardManager), highlightManager_(highlightManager), + emoticons_(emoticons), clientBlockListManager_(clientBlockListManager), inviteUserSearchController_(inviteUserSearchController), vcardManager_(vcardManager) { timerFactory_ = timerFactory; eventController_ = eventController; stanzaChannel_ = stanzaChannel; iqRouter_ = iqRouter; chatWindowFactory_ = chatWindowFactory; nickResolver_ = nickResolver; presenceOracle_ = presenceOracle; avatarManager_ = NULL; serverDiscoInfo_ = boost::make_shared<DiscoInfo>(); presenceSender_ = presenceSender; uiEventStream_ = uiEventStream; mucBookmarkManager_ = NULL; profileSettings_ = profileSettings; - chatMessageParser_ = new ChatMessageParser(emoticons); presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1)); uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1)); chatListWindow_ = chatListWindowFactory->createChatListWindow(uiEventStream_); chatListWindow_->onMUCBookmarkActivated.connect(boost::bind(&ChatsManager::handleMUCBookmarkActivated, this, _1)); chatListWindow_->onRecentActivated.connect(boost::bind(&ChatsManager::handleRecentActivated, this, _1)); chatListWindow_->onClearRecentsRequested.connect(boost::bind(&ChatsManager::handleClearRecentsRequested, this)); joinMUCWindow_ = NULL; mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, profileSettings_); mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1)); ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1)); whiteboardManager_->onSessionRequest.connect(boost::bind(&ChatsManager::handleWhiteboardSessionRequest, this, _1, _2)); whiteboardManager_->onRequestAccepted.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardAccepted)); whiteboardManager_->onSessionTerminate.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardTerminated)); whiteboardManager_->onRequestRejected.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardRejected)); roster_->onJIDAdded.connect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1)); roster_->onJIDRemoved.connect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1)); roster_->onJIDUpdated.connect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1)); roster_->onRosterCleared.connect(boost::bind(&ChatsManager::handleRosterCleared, this)); settings_->onSettingChanged.connect(boost::bind(&ChatsManager::handleSettingChanged, this, _1)); userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS); setupBookmarks(); loadRecents(); autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(jid.getDomain(), roster_, settings_); } ChatsManager::~ChatsManager() { settings_->onSettingChanged.disconnect(boost::bind(&ChatsManager::handleSettingChanged, this, _1)); roster_->onJIDAdded.disconnect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1)); roster_->onJIDRemoved.disconnect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1)); roster_->onJIDUpdated.disconnect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1)); roster_->onRosterCleared.disconnect(boost::bind(&ChatsManager::handleRosterCleared, this)); delete joinMUCWindow_; foreach (JIDChatControllerPair controllerPair, chatControllers_) { delete controllerPair.second; } foreach (JIDMUCControllerPair controllerPair, mucControllers_) { delete controllerPair.second; } delete mucBookmarkManager_; delete mucSearchController_; - delete chatMessageParser_; delete autoAcceptMUCInviteDecider_; } void ChatsManager::saveRecents() { std::stringstream serializeStream; boost::archive::text_oarchive oa(serializeStream); std::vector<ChatListWindow::Chat> recentsLimited = std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end()); if (recentsLimited.size() > 25) { recentsLimited.erase(recentsLimited.begin() + 25, recentsLimited.end()); } if (eagleMode_) { foreach(ChatListWindow::Chat& chat, recentsLimited) { chat.activity = ""; } } oa << recentsLimited; std::string serializedStr = Base64::encode(createByteArray(serializeStream.str())); profileSettings_->storeString(RECENT_CHATS, serializedStr); } void ChatsManager::handleClearRecentsRequested() { recentChats_.clear(); saveRecents(); handleUnreadCountChanged(NULL); } void ChatsManager::handleJIDAddedToRoster(const JID &jid) { updatePresenceReceivingStateOnChatController(jid); } void ChatsManager::handleJIDRemovedFromRoster(const JID &jid) { updatePresenceReceivingStateOnChatController(jid); } @@ -665,71 +664,72 @@ void ChatsManager::setOnline(bool enabled) { delete mucBookmarkManager_; mucBookmarkManager_ = NULL; chatListWindow_->setBookmarksEnabled(false); markAllRecentsOffline(); } else { setupBookmarks(); localMUCServiceFinderWalker_ = boost::make_shared<DiscoServiceWalker>(jid_.getDomain(), iqRouter_); localMUCServiceFinderWalker_->onServiceFound.connect(boost::bind(&ChatsManager::handleLocalServiceFound, this, _1, _2)); localMUCServiceFinderWalker_->onWalkAborted.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this)); localMUCServiceFinderWalker_->onWalkComplete.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this)); localMUCServiceFinderWalker_->beginWalk(); } } void ChatsManager::handleChatRequest(const std::string &contact) { ChatController* controller = getChatControllerOrFindAnother(JID(contact)); controller->activateChatWindow(); } ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact) { ChatController* controller = getChatControllerIfExists(contact); if (!controller && !mucRegistry_->isMUC(contact.toBare())) { foreach (JIDChatControllerPair pair, chatControllers_) { if (pair.first.toBare() == contact.toBare()) { controller = pair.second; break; } } } return controller ? controller : createNewChatController(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_, chatMessageParser_, autoAcceptMUCInviteDecider_); + boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), false); /* a message parser that knows this is a chat (not a room/MUC) */ + ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, autoAcceptMUCInviteDecider_); chatControllers_[contact] = controller; controller->setAvailableServerFeatures(serverDiscoInfo_); controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false)); controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); controller->onConvertToMUC.connect(boost::bind(&ChatsManager::handleTransformChatToMUC, this, controller, _1, _2, _3)); updatePresenceReceivingStateOnChatController(contact); controller->setCanStartImpromptuChats(!localMUCServiceJID_.toString().empty()); return controller; } ChatController* ChatsManager::getChatControllerOrCreate(const JID &contact) { ChatController* controller = getChatControllerIfExists(contact); return controller ? controller : createNewChatController(contact); } ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool rebindIfNeeded) { if (chatControllers_.find(contact) == chatControllers_.end()) { if (mucRegistry_->isMUC(contact.toBare())) { return NULL; } //Need to look for an unbound window to bind first JID bare(contact.toBare()); if (chatControllers_.find(bare) != chatControllers_.end()) { rebindControllerJID(bare, contact); } else { foreach (JIDChatControllerPair pair, chatControllers_) { if (pair.first.toBare() == contact.toBare()) { if (rebindIfNeeded) { rebindControllerJID(pair.first, contact); return chatControllers_[contact]; } else { return pair.second; } } } @@ -749,71 +749,72 @@ MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::opti MUC::ref muc; if (!stanzaChannel_->isAvailable()) { /* This is potentially not the optimal solution, but it will avoid consistency issues.*/ return muc; } if (addAutoJoin) { MUCBookmark bookmark(mucJID, mucJID.getNode()); bookmark.setAutojoin(true); if (nickMaybe) { bookmark.setNick(*nickMaybe); } if (password) { bookmark.setPassword(*password); } mucBookmarkManager_->addBookmark(bookmark); } std::map<JID, MUCController*>::iterator it = mucControllers_.find(mucJID); if (it != mucControllers_.end()) { it->second->rejoin(); } else { std::string nick = (nickMaybe && !(*nickMaybe).empty()) ? nickMaybe.get() : nickResolver_->jidToNick(jid_); muc = mucManager->createMUC(mucJID); if (createAsReservedIfNew) { muc->setCreateAsReservedIfNew(); } if (isImpromptu) { muc->setCreateAsReservedIfNew(); } MUCController* controller = NULL; SingleChatWindowFactoryAdapter* chatWindowFactoryAdapter = NULL; if (reuseChatwindow) { chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow); } - controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser_, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_); + boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), true); /* a message parser that knows this is a room/MUC (not a chat) */ + controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_); if (chatWindowFactoryAdapter) { /* The adapters are only passed to chat windows, which are deleted in their * controllers' dtor, which are deleted in ChatManager's dtor. The adapters * are also deleted there.*/ chatWindowFactoryAdapters_[controller] = chatWindowFactoryAdapter; } mucControllers_[mucJID] = controller; controller->setAvailableServerFeatures(serverDiscoInfo_); controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller)); controller->onUserJoined.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), "", true)); controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), _1, true)); controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); handleChatActivity(mucJID.toBare(), "", true); } mucControllers_[mucJID]->showChatWindow(); return muc; } void ChatsManager::handleSearchMUCRequest() { mucSearchController_->openSearchWindow(); } void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) { JID jid = message->getFrom(); boost::shared_ptr<MessageEvent> event(new MessageEvent(message)); bool isInvite = !!message->getPayload<MUCInvitationPayload>(); bool isMediatedInvite = (message->getPayload<MUCUserPayload>() && message->getPayload<MUCUserPayload>()->getInvite()); if (isMediatedInvite) { jid = (*message->getPayload<MUCUserPayload>()->getInvite()).from; } if (!event->isReadable() && !message->getPayload<ChatState>() && !message->getPayload<DeliveryReceipt>() && !message->getPayload<DeliveryReceiptRequest>() && !isInvite && !isMediatedInvite && !message->hasSubject()) { return; } diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index 88a0986..41435d9 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -142,45 +142,45 @@ namespace Swift { std::map<JID, MUCController*> mucControllers_; std::map<JID, ChatController*> chatControllers_; std::map<ChatControllerBase*, SingleChatWindowFactoryAdapter*> chatWindowFactoryAdapters_; EventController* eventController_; JID jid_; StanzaChannel* stanzaChannel_; IQRouter* iqRouter_; ChatWindowFactory* chatWindowFactory_; JoinMUCWindowFactory* joinMUCWindowFactory_; NickResolver* nickResolver_; PresenceOracle* presenceOracle_; AvatarManager* avatarManager_; PresenceSender* presenceSender_; UIEventStream* uiEventStream_; MUCBookmarkManager* mucBookmarkManager_; boost::shared_ptr<DiscoInfo> serverDiscoInfo_; ChatListWindow* chatListWindow_; JoinMUCWindow* joinMUCWindow_; boost::bsignals::scoped_connection uiEventConnection_; bool useDelayForLatency_; TimerFactory* timerFactory_; MUCRegistry* mucRegistry_; EntityCapsProvider* entityCapsProvider_; MUCManager* mucManager; MUCSearchController* mucSearchController_; std::list<ChatListWindow::Chat> recentChats_; ProfileSettingsProvider* profileSettings_; FileTransferOverview* ftOverview_; XMPPRoster* roster_; bool eagleMode_; bool userWantsReceipts_; SettingsProvider* settings_; HistoryController* historyController_; WhiteboardManager* whiteboardManager_; HighlightManager* highlightManager_; + std::map<std::string, std::string> emoticons_; ClientBlockListManager* clientBlockListManager_; - ChatMessageParser* chatMessageParser_; JID localMUCServiceJID_; boost::shared_ptr<DiscoServiceWalker> localMUCServiceFinderWalker_; AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; UserSearchController* inviteUserSearchController_; IDGenerator idGenerator_; VCardManager* vcardManager_; }; } diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index d09bc3d..b467227 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -39,71 +39,71 @@ #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> #include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h> #include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000 namespace Swift { /** * The controller does not gain ownership of the stanzaChannel, nor the factory. */ MUCController::MUCController ( const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* uiEventStream, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, - ChatMessageParser* chatMessageParser, + boost::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager) : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false) { parting_ = true; joined_ = false; lastWasPresence_ = false; shouldJoinOnReconnect_ = true; doneGettingHistory_ = false; events_ = uiEventStream; xmppRoster_ = roster; roster_ = new Roster(false, true); rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithResource); completer_ = new TabComplete(); chatWindow_->setRosterModel(roster_); chatWindow_->setTabComplete(completer_); chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this)); chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1)); chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2)); chatWindow_->onChangeSubjectRequest.connect(boost::bind(&MUCController::handleChangeSubjectRequest, this, _1)); chatWindow_->onBookmarkRequest.connect(boost::bind(&MUCController::handleBookmarkRequest, this)); chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1)); chatWindow_->onConfigurationFormCancelled.connect(boost::bind(&MUCController::handleConfigurationCancelled, this)); chatWindow_->onDestroyRequest.connect(boost::bind(&MUCController::handleDestroyRoomRequest, this)); chatWindow_->onInviteToChat.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this, _1)); chatWindow_->onGetAffiliationsRequest.connect(boost::bind(&MUCController::handleGetAffiliationsRequest, this)); chatWindow_->onChangeAffiliationsRequest.connect(boost::bind(&MUCController::handleChangeAffiliationsRequest, this, _1)); muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1)); muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1)); muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1)); muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1)); muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3)); muc_->onRoleChangeFailed.connect(boost::bind(&MUCController::handleOccupantRoleChangeFailed, this, _1, _2, _3)); muc_->onAffiliationListReceived.connect(boost::bind(&MUCController::handleAffiliationListReceived, this, _1, _2)); diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index feffaba..b5b5837 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -18,71 +18,71 @@ #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> namespace Swift { class StanzaChannel; class IQRouter; class ChatWindowFactory; class Roster; class AvatarManager; class UIEventStream; class TimerFactory; class TabComplete; class XMPPRoster; class HighlightManager; class UIEvent; class VCardManager; class RosterVCardProvider; enum JoinPart {Join, Part, JoinThenPart, PartThenJoin}; struct NickJoinPart { NickJoinPart(const std::string& nick, JoinPart type) : nick(nick), type(type) {} std::string nick; JoinPart type; }; 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, ChatMessageParser* chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager); + MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager); virtual ~MUCController(); boost::signal<void ()> onUserLeft; boost::signal<void ()> onUserJoined; boost::signal<void ()> onImpromptuConfigCompleted; virtual void setOnline(bool online); void rejoin(); static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent); static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu); static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts); bool isJoined(); const std::string& getNick(); const boost::optional<std::string> getPassword() const; bool isImpromptu() const; std::map<std::string, JID> getParticipantJIDs() const; void sendInvites(const std::vector<JID>& jids, const std::string& reason) const; protected: void preSendMessageRequest(boost::shared_ptr<Message> message); bool isIncomingMessageFromMe(boost::shared_ptr<Message> message); std::string senderDisplayNameFromMessage(const JID& from); boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message> message) const; void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>); void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>, const HighlightAction&); void cancelReplaces(); void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming); private: void setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role); void clearPresenceQueue(); void addPresenceMessage(const std::string& message); void handleWindowOccupantSelectionChanged(ContactRosterItem* item); void handleActionRequestedOnOccupant(ChatWindow::OccupantAction, ContactRosterItem* item); void handleWindowClosed(); void handleAvatarChanged(const JID& jid); void handleOccupantJoined(const MUCOccupant& occupant); diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp index 0a14303..5dca63a 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp @@ -16,110 +16,216 @@ class ChatMessageParserTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(ChatMessageParserTest); CPPUNIT_TEST(testFullBody); CPPUNIT_TEST(testOneEmoticon); CPPUNIT_TEST(testBareEmoticon); CPPUNIT_TEST(testHiddenEmoticon); CPPUNIT_TEST(testEndlineEmoticon); CPPUNIT_TEST(testBoundedEmoticons); CPPUNIT_TEST_SUITE_END(); public: void setUp() { smile1_ = ":)"; smile1Path_ = "/blah/smile1.png"; smile2_ = ":("; smile2Path_ = "/blah/smile2.jpg"; emoticons_[smile1_] = smile1Path_; emoticons_[smile2_] = smile2Path_; } void tearDown() { emoticons_.clear(); } void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { boost::shared_ptr<ChatWindow::ChatTextMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]); CPPUNIT_ASSERT_EQUAL(text, part->text); } void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) { boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]); CPPUNIT_ASSERT(!!part); CPPUNIT_ASSERT_EQUAL(text, part->alternativeText); CPPUNIT_ASSERT_EQUAL(path, part->imagePath); } + void assertHighlight(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(result.getParts()[index]); + CPPUNIT_ASSERT_EQUAL(text, part->text); + } + void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { boost::shared_ptr<ChatWindow::ChatURIMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]); CPPUNIT_ASSERT_EQUAL(text, part->target); } + static HighlightRule ruleFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) + { + HighlightRule rule; + std::vector<std::string> keywords; + keywords.push_back(keyword); + rule.setKeywords(keywords); + rule.setMatchCase(matchCase); + rule.setMatchWholeWords(matchWholeWord); + rule.setMatchChat(true); + return rule; + } + + static const HighlightRulesListPtr ruleListFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) + { + boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); + list->addRule(ruleFromKeyword(keyword, matchCase, matchWholeWord)); + return list; + } + + static const HighlightRulesListPtr ruleListFromKeywords(const HighlightRule &rule1, const HighlightRule &rule2) + { + boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); + list->addRule(rule1); + list->addRule(rule2); + return list; + } + void testFullBody() { - ChatMessageParser testling(emoticons_); - ChatWindow::ChatMessage result = testling.parseMessageBody(":) shiny :( :) http://wonderland.lit/blah http://denmark.lit boom boom"); + const std::string no_special_message = "a message with no special content"; + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(no_special_message); + assertText(result, 0, no_special_message); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody(":) shiny :( trigger :) http://wonderland.lit/blah http://denmark.lit boom boom"); assertEmoticon(result, 0, smile1_, smile1Path_); assertText(result, 1, " shiny "); assertEmoticon(result, 2, smile2_, smile2Path_); assertText(result, 3, " "); - assertEmoticon(result, 4, smile1_, smile1Path_); + assertHighlight(result, 4, "trigger"); assertText(result, 5, " "); - assertURL(result, 6, "http://wonderland.lit/blah"); + assertEmoticon(result, 6, smile1_, smile1Path_); assertText(result, 7, " "); - assertURL(result, 8, "http://denmark.lit"); - assertText(result, 9, " boom boom"); + assertURL(result, 8, "http://wonderland.lit/blah"); + assertText(result, 9, " "); + assertURL(result, 10, "http://denmark.lit"); + assertText(result, 11, " boom boom"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody("testtriggermessage"); + assertText(result, 0, "test"); + assertHighlight(result, 1, "trigger"); + assertText(result, 2, "message"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, true)); + result = testling.parseMessageBody("testtriggermessage"); + assertText(result, 0, "testtriggermessage"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", true, false)); + result = testling.parseMessageBody("TrIgGeR"); + assertText(result, 0, "TrIgGeR"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody("TrIgGeR"); + assertHighlight(result, 0, "TrIgGeR"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody("partialTrIgGeRmatch"); + assertText(result, 0, "partial"); + assertHighlight(result, 1, "TrIgGeR"); + assertText(result, 2, "match"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zero one two three"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "one"); + assertText(result, 2, " two "); + assertHighlight(result, 3, "three"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "oNe"); + assertText(result, 2, " two "); + assertHighlight(result, 3, "tHrEe"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", true, false))); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "oNe"); + assertText(result, 2, " two tHrEe"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", true, false))); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero oNe two tHrEe"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zeroonetwothree"); + assertText(result, 0, "zero"); + assertHighlight(result, 1, "one"); + assertText(result, 2, "two"); + assertHighlight(result, 3, "three"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zeroOnEtwoThReE"); + assertText(result, 0, "zeroOnEtwo"); + assertHighlight(result, 1, "ThReE"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zeroonetwothree"); + assertText(result, 0, "zeroonetwo"); + assertHighlight(result, 1, "three"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, true))); + result = testling.parseMessageBody("zeroonetwothree"); + assertText(result, 0, "zeroonetwothree"); } void testOneEmoticon() { - ChatMessageParser testling(emoticons_); + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody(" :) "); assertText(result, 0, " "); assertEmoticon(result, 1, smile1_, smile1Path_); assertText(result, 2, " "); } void testBareEmoticon() { - ChatMessageParser testling(emoticons_); + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody(":)"); assertEmoticon(result, 0, smile1_, smile1Path_); } void testHiddenEmoticon() { - ChatMessageParser testling(emoticons_); + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a"); assertText(result, 0, "b:)a"); } void testEndlineEmoticon() { - ChatMessageParser testling(emoticons_); + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody("Lazy:)"); assertText(result, 0, "Lazy"); assertEmoticon(result, 1, smile1_, smile1Path_); } void testBoundedEmoticons() { - ChatMessageParser testling(emoticons_); + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody(":)Lazy:("); assertEmoticon(result, 0, smile1_, smile1Path_); assertText(result, 1, "Lazy"); assertEmoticon(result, 2, smile2_, smile2Path_); } void testEmoticonParenthesis() { - ChatMessageParser testling(emoticons_); + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))"); assertText(result, 0, "(Like this "); assertEmoticon(result, 1, smile1_, smile1Path_); assertText(result, 2, ")"); } - private: std::map<std::string, std::string> emoticons_; std::string smile1_; std::string smile1Path_; std::string smile2_; std::string smile2Path_; }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageParserTest); - diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp index 7268878..bb22e43 100644 --- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp @@ -1,37 +1,37 @@ /* - * Copyright (c) 2010-2013 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <boost/algorithm/string.hpp> #include <hippomocks.h> #include "Swiften/Base/foreach.h" #include "Swift/Controllers/XMPPEvents/EventController.h" #include "Swiften/Presence/DirectedPresenceSender.h" #include "Swiften/Presence/StanzaChannelPresenceSender.h" #include "Swiften/Avatars/NullAvatarManager.h" #include "Swift/Controllers/Chat/MUCController.h" #include "Swift/Controllers/UIInterfaces/ChatWindow.h" #include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h" #include "Swiften/Client/NickResolver.h" #include "Swiften/Roster/XMPPRoster.h" #include "Swift/Controllers/UIEvents/UIEventStream.h" #include "Swift/Controllers/UnitTest/MockChatWindow.h" #include "Swiften/MUC/UnitTest/MockMUC.h" #include "Swiften/Client/DummyStanzaChannel.h" #include "Swiften/Queries/DummyIQChannel.h" #include "Swiften/Presence/PresenceOracle.h" #include "Swiften/Network/TimerFactory.h" #include "Swiften/Elements/MUCUserPayload.h" #include "Swiften/Disco/DummyEntityCapsProvider.h" #include <Swiften/VCards/VCardMemoryStorage.h> #include <Swiften/Crypto/PlatformCryptoProvider.h> #include <Swiften/VCards/VCardManager.h> #include <Swift/Controllers/Settings/DummySettingsProvider.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> #include <Swift/Controllers/Chat/UserSearchController.h> #include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h> @@ -49,95 +49,94 @@ class MUCControllerTest : public CppUnit::TestFixture { CPPUNIT_TEST(testAddressedToSelf); CPPUNIT_TEST(testNotAddressedToSelf); CPPUNIT_TEST(testAddressedToSelfBySelf); CPPUNIT_TEST(testMessageWithEmptyLabelItem); CPPUNIT_TEST(testMessageWithLabelItem); CPPUNIT_TEST(testCorrectMessageWithLabelItem); CPPUNIT_TEST(testRoleAffiliationStates); CPPUNIT_TEST_SUITE_END(); public: void setUp() { crypto_ = boost::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create()); self_ = JID("girl@wonderland.lit/rabbithole"); nick_ = "aLiCe"; mucJID_ = JID("teaparty@rooms.wonderland.lit"); mocks_ = new MockRepository(); stanzaChannel_ = new DummyStanzaChannel(); iqChannel_ = new DummyIQChannel(); iqRouter_ = new IQRouter(iqChannel_); eventController_ = new EventController(); chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>(); presenceOracle_ = new PresenceOracle(stanzaChannel_); presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); directedPresenceSender_ = new DirectedPresenceSender(presenceSender_); uiEventStream_ = new UIEventStream(); avatarManager_ = new NullAvatarManager(); TimerFactory* timerFactory = NULL; window_ = new MockChatWindow(); mucRegistry_ = new MUCRegistry(); entityCapsProvider_ = new DummyEntityCapsProvider(); settings_ = new DummySettingsProvider(); highlightManager_ = new HighlightManager(settings_); muc_ = boost::make_shared<MockMUC>(mucJID_); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); - chatMessageParser_ = new ChatMessageParser(std::map<std::string, std::string>()); + chatMessageParser_ = boost::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getRules(), true); vcardStorage_ = new VCardMemoryStorage(crypto_.get()); vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_); controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_, false, NULL, vcardManager_); } void tearDown() { delete controller_; delete vcardManager_; delete vcardStorage_; delete highlightManager_; delete settings_; delete entityCapsProvider_; delete eventController_; delete presenceOracle_; delete mocks_; delete uiEventStream_; delete stanzaChannel_; delete presenceSender_; delete directedPresenceSender_; delete iqRouter_; delete iqChannel_; delete mucRegistry_; delete avatarManager_; - delete chatMessageParser_; } void finishJoin() { Presence::ref presence(new Presence()); presence->setFrom(JID(muc_->getJID().toString() + "/" + nick_)); MUCUserPayload::ref status(new MUCUserPayload()); MUCUserPayload::StatusCode code; code.code = 110; status->addStatusCode(code); presence->addPayload(status); stanzaChannel_->onPresenceReceived(presence); } void testAddressedToSelf() { finishJoin(); Message::ref message(new Message()); message = Message::ref(new Message()); message->setFrom(JID(muc_->getJID().toString() + "/otherperson")); message->setBody("basic " + nick_ + " test."); message->setType(Message::Groupchat); controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); CPPUNIT_ASSERT_EQUAL((size_t)1, eventController_->getEvents().size()); message = Message::ref(new Message()); message->setFrom(JID(muc_->getJID().toString() + "/otherperson")); message->setBody(nick_ + ": hi there"); message->setType(Message::Groupchat); controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); CPPUNIT_ASSERT_EQUAL((size_t)2, eventController_->getEvents().size()); message->setFrom(JID(muc_->getJID().toString() + "/other")); message->setBody("Hi there " + nick_); message->setType(Message::Groupchat); controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); @@ -397,43 +396,43 @@ public: foreach(RosterItem* childItem, child->getChildren()) { ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(childItem); CPPUNIT_ASSERT(item); std::map<std::string, MUCOccupant>::const_iterator occupant = occupants.find(item->getJID().getResource()); CPPUNIT_ASSERT(occupant != occupants.end()); CPPUNIT_ASSERT(item->getMUCRole() == occupant->second.getRole()); CPPUNIT_ASSERT(item->getMUCAffiliation() == occupant->second.getAffiliation()); } } } private: JID self_; JID mucJID_; MockMUC::ref muc_; std::string nick_; DummyStanzaChannel* stanzaChannel_; DummyIQChannel* iqChannel_; IQRouter* iqRouter_; EventController* eventController_; ChatWindowFactory* chatWindowFactory_; UserSearchWindowFactory* userSearchWindowFactory_; MUCController* controller_; // NickResolver* nickResolver_; PresenceOracle* presenceOracle_; AvatarManager* avatarManager_; StanzaChannelPresenceSender* presenceSender_; DirectedPresenceSender* directedPresenceSender_; MockRepository* mocks_; UIEventStream* uiEventStream_; MockChatWindow* window_; MUCRegistry* mucRegistry_; DummyEntityCapsProvider* entityCapsProvider_; DummySettingsProvider* settings_; HighlightManager* highlightManager_; - ChatMessageParser* chatMessageParser_; + boost::shared_ptr<ChatMessageParser> chatMessageParser_; boost::shared_ptr<CryptoProvider> crypto_; VCardManager* vcardManager_; VCardMemoryStorage* vcardStorage_; }; CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest); diff --git a/Swift/Controllers/HighlightAction.h b/Swift/Controllers/HighlightAction.h index bfbed74..de1f201 100644 --- a/Swift/Controllers/HighlightAction.h +++ b/Swift/Controllers/HighlightAction.h @@ -1,45 +1,76 @@ /* * Copyright (c) 2012 Maciej Niedzielski * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #pragma once #include <string> +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.hpp> + namespace Swift { class HighlightRule; class HighlightAction { public: HighlightAction() : highlightText_(false), playSound_(false) {} bool highlightText() const { return highlightText_; } void setHighlightText(bool highlightText); + /** + * Gets the foreground highlight color. If the string is empty, assume a default color. + */ const std::string& getTextColor() const { return textColor_; } void setTextColor(const std::string& textColor) { textColor_ = textColor; } + /** + * Gets the background highlight color. If the string is empty, assume a default color. + */ const std::string& getTextBackground() const { return textBackground_; } void setTextBackground(const std::string& textBackground) { textBackground_ = textBackground; } bool playSound() const { return playSound_; } void setPlaySound(bool playSound); + /** + * Gets the sound filename. If the string is empty, assume a default sound file. + */ const std::string& getSoundFile() const { return soundFile_; } void setSoundFile(const std::string& soundFile) { soundFile_ = soundFile; } bool isEmpty() const { return !highlightText_ && !playSound_; } private: + friend class boost::serialization::access; + template<class Archive> void serialize(Archive & ar, const unsigned int version); + bool highlightText_; std::string textColor_; std::string textBackground_; bool playSound_; std::string soundFile_; }; + template<class Archive> + void HighlightAction::serialize(Archive& ar, const unsigned int /*version*/) + { + ar & highlightText_; + ar & textColor_; + ar & textBackground_; + ar & playSound_; + ar & soundFile_; + } + } diff --git a/Swift/Controllers/HighlightEditorController.cpp b/Swift/Controllers/HighlightEditorController.cpp index 899e4bb..38007f0 100644 --- a/Swift/Controllers/HighlightEditorController.cpp +++ b/Swift/Controllers/HighlightEditorController.cpp @@ -1,40 +1,56 @@ /* * Copyright (c) 2012 Maciej Niedzielski * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #include <boost/bind.hpp> #include <Swift/Controllers/HighlightEditorController.h> -#include <Swift/Controllers/UIInterfaces/HighlightEditorWidget.h> -#include <Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h> #include <Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h> +#include <Swift/Controllers/ContactSuggester.h> namespace Swift { -HighlightEditorController::HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWidgetFactory* highlightEditorWidgetFactory, HighlightManager* highlightManager) : highlightEditorWidgetFactory_(highlightEditorWidgetFactory), highlightEditorWidget_(NULL), highlightManager_(highlightManager) +HighlightEditorController::HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWindowFactory* highlightEditorWindowFactory, HighlightManager* highlightManager) +: highlightEditorWindowFactory_(highlightEditorWindowFactory), highlightEditorWindow_(NULL), highlightManager_(highlightManager), contactSuggester_(0) { uiEventStream->onUIEvent.connect(boost::bind(&HighlightEditorController::handleUIEvent, this, _1)); } HighlightEditorController::~HighlightEditorController() { - delete highlightEditorWidget_; - highlightEditorWidget_ = NULL; + delete highlightEditorWindow_; + highlightEditorWindow_ = NULL; } void HighlightEditorController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) { boost::shared_ptr<RequestHighlightEditorUIEvent> event = boost::dynamic_pointer_cast<RequestHighlightEditorUIEvent>(rawEvent); if (event) { - if (!highlightEditorWidget_) { - highlightEditorWidget_ = highlightEditorWidgetFactory_->createHighlightEditorWidget(); - highlightEditorWidget_->setHighlightManager(highlightManager_); + if (!highlightEditorWindow_) { + highlightEditorWindow_ = highlightEditorWindowFactory_->createHighlightEditorWindow(); + highlightEditorWindow_->setHighlightManager(highlightManager_); + highlightEditorWindow_->onContactSuggestionsRequested.connect(boost::bind(&HighlightEditorController::handleContactSuggestionsRequested, this, _1)); } - highlightEditorWidget_->show(); + highlightEditorWindow_->show(); + } +} + +void HighlightEditorController::handleContactSuggestionsRequested(const std::string& text) +{ + if (contactSuggester_) { + highlightEditorWindow_->setContactSuggestions(contactSuggester_->getSuggestions(text)); } } } diff --git a/Swift/Controllers/HighlightEditorController.h b/Swift/Controllers/HighlightEditorController.h index 3868251..54322e2 100644 --- a/Swift/Controllers/HighlightEditorController.h +++ b/Swift/Controllers/HighlightEditorController.h @@ -1,38 +1,48 @@ /* * Copyright (c) 2012 Maciej Niedzielski * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #pragma once #include <boost/shared_ptr.hpp> #include <Swift/Controllers/UIEvents/UIEvent.h> namespace Swift { class UIEventStream; - class HighlightEditorWidgetFactory; - class HighlightEditorWidget; + class HighlightEditorWindowFactory; + class HighlightEditorWindow; class HighlightManager; + class ContactSuggester; class HighlightEditorController { public: - HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWidgetFactory* highlightEditorWidgetFactory, HighlightManager* highlightManager); + HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWindowFactory* highlightEditorWindowFactory, HighlightManager* highlightManager); ~HighlightEditorController(); HighlightManager* getHighlightManager() const { return highlightManager_; } + void setContactSuggester(ContactSuggester *suggester) { contactSuggester_ = suggester; } private: void handleUIEvent(boost::shared_ptr<UIEvent> event); + void handleContactSuggestionsRequested(const std::string& text); private: - HighlightEditorWidgetFactory* highlightEditorWidgetFactory_; - HighlightEditorWidget* highlightEditorWidget_; + HighlightEditorWindowFactory* highlightEditorWindowFactory_; + HighlightEditorWindow* highlightEditorWindow_; HighlightManager* highlightManager_; + ContactSuggester* contactSuggester_; }; } diff --git a/Swift/Controllers/HighlightManager.cpp b/Swift/Controllers/HighlightManager.cpp index 7ab578e..eac562f 100644 --- a/Swift/Controllers/HighlightManager.cpp +++ b/Swift/Controllers/HighlightManager.cpp @@ -1,139 +1,131 @@ /* * Copyright (c) 2012 Maciej Niedzielski * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #include <cassert> #include <boost/algorithm/string.hpp> #include <boost/regex.hpp> #include <boost/bind.hpp> #include <boost/numeric/conversion/cast.hpp> +#include <boost/serialization/vector.hpp> +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.hpp> #include <Swiften/Base/foreach.h> #include <Swift/Controllers/HighlightManager.h> #include <Swift/Controllers/Highlighter.h> #include <Swift/Controllers/Settings/SettingsProvider.h> #include <Swift/Controllers/SettingConstants.h> /* How does highlighting work? * * HighlightManager manages a list of if-then rules used to highlight messages. * Rule is represented by HighlightRule. Action ("then" part) is HighlightAction. * * * HighlightManager is also used as a factory for Highlighter objects. * Each ChatControllerBase has its own Highlighter. * Highligher may be customized by using setNick(), etc. * * ChatControllerBase passes incoming messages to Highlighter and gets HighlightAction in return * (first matching rule is returned). * If needed, HighlightAction is then passed back to Highlighter for further handling. * This results in HighlightManager emiting onHighlight event, * which is handled by SoundController to play sound notification */ namespace Swift { HighlightManager::HighlightManager(SettingsProvider* settings) : settings_(settings) , storingSettings_(false) { + rules_ = boost::make_shared<HighlightRulesList>(); loadSettings(); settings_->onSettingChanged.connect(boost::bind(&HighlightManager::handleSettingChanged, this, _1)); } void HighlightManager::handleSettingChanged(const std::string& settingPath) { if (!storingSettings_ && SettingConstants::HIGHLIGHT_RULES.getKey() == settingPath) { loadSettings(); } } -void HighlightManager::loadSettings() -{ - std::string highlightRules = settings_->getSetting(SettingConstants::HIGHLIGHT_RULES); - if (highlightRules == "@") { - rules_ = getDefaultRules(); - } else { - rules_ = rulesFromString(highlightRules); - } -} - std::string HighlightManager::rulesToString() const { - std::string s; - foreach (HighlightRule r, rules_) { - s += r.toString() + '\f'; - } - if (s.size()) { - s.erase(s.end() - 1); - } - return s; -} - -std::vector<HighlightRule> HighlightManager::rulesFromString(const std::string& rulesString) -{ - std::vector<HighlightRule> rules; - std::string s(rulesString); - typedef boost::split_iterator<std::string::iterator> split_iterator; - for (split_iterator it = boost::make_split_iterator(s, boost::first_finder("\f")); it != split_iterator(); ++it) { - HighlightRule r = HighlightRule::fromString(boost::copy_range<std::string>(*it)); - if (!r.isEmpty()) { - rules.push_back(r); - } - } - return rules; + std::stringstream stream; + boost::archive::text_oarchive archive(stream); + archive << rules_->list_; + return stream.str(); } std::vector<HighlightRule> HighlightManager::getDefaultRules() { std::vector<HighlightRule> rules; HighlightRule r; r.setMatchChat(true); r.getAction().setPlaySound(true); rules.push_back(r); return rules; } -void HighlightManager::storeSettings() -{ - storingSettings_ = true; // don't reload settings while saving - settings_->storeSetting(SettingConstants::HIGHLIGHT_RULES, rulesToString()); - storingSettings_ = false; -} - HighlightRule HighlightManager::getRule(int index) const { - assert(index >= 0 && static_cast<size_t>(index) < rules_.size()); - return rules_[static_cast<size_t>(index)]; + assert(index >= 0 && static_cast<size_t>(index) < rules_->getSize()); + return rules_->getRule(static_cast<size_t>(index)); } void HighlightManager::setRule(int index, const HighlightRule& rule) { - assert(index >= 0 && static_cast<size_t>(index) < rules_.size()); - rules_[static_cast<size_t>(index)] = rule; - storeSettings(); + assert(index >= 0 && static_cast<size_t>(index) < rules_->getSize()); + rules_->list_[static_cast<size_t>(index)] = rule; } void HighlightManager::insertRule(int index, const HighlightRule& rule) { - assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) <= rules_.size()); - rules_.insert(rules_.begin() + index, rule); - storeSettings(); + assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) <= rules_->getSize()); + rules_->list_.insert(rules_->list_.begin() + index, rule); } void HighlightManager::removeRule(int index) { - assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) < rules_.size()); - rules_.erase(rules_.begin() + index); - storeSettings(); + assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) < rules_->getSize()); + rules_->list_.erase(rules_->list_.begin() + index); +} + +void HighlightManager::storeSettings() +{ + storingSettings_ = true; // don't reload settings while saving + settings_->storeSetting(SettingConstants::HIGHLIGHT_RULES, rulesToString()); + storingSettings_ = false; +} + +void HighlightManager::loadSettings() +{ + std::string rulesString = settings_->getSetting(SettingConstants::HIGHLIGHT_RULES); + std::stringstream stream; + stream << rulesString; + try { + boost::archive::text_iarchive archive(stream); + archive >> rules_->list_; + } catch (boost::archive::archive_exception&) { + rules_->list_ = getDefaultRules(); + } } Highlighter* HighlightManager::createHighlighter() { return new Highlighter(this); } } diff --git a/Swift/Controllers/HighlightManager.h b/Swift/Controllers/HighlightManager.h index d195d05..3da72eb 100644 --- a/Swift/Controllers/HighlightManager.h +++ b/Swift/Controllers/HighlightManager.h @@ -1,49 +1,71 @@ /* * Copyright (c) 2012 Maciej Niedzielski * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #pragma once #include <vector> #include <string> #include <Swiften/Base/boost_bsignals.h> #include <Swift/Controllers/HighlightRule.h> namespace Swift { class SettingsProvider; class Highlighter; class HighlightManager { public: + + class HighlightRulesList { + public: + friend class HighlightManager; + size_t getSize() const { return list_.size(); } + const HighlightRule& getRule(const size_t index) const { return list_[index]; } + void addRule(const HighlightRule &rule) { list_.push_back(rule); } + void combineRules(const HighlightRulesList &rhs) { + list_.insert(list_.end(), rhs.list_.begin(), rhs.list_.end()); + } + private: + std::vector<HighlightRule> list_; + }; + HighlightManager(SettingsProvider* settings); Highlighter* createHighlighter(); - const std::vector<HighlightRule>& getRules() const { return rules_; } + boost::shared_ptr<const HighlightManager::HighlightRulesList> getRules() const { return rules_; } + HighlightRule getRule(int index) const; void setRule(int index, const HighlightRule& rule); void insertRule(int index, const HighlightRule& rule); void removeRule(int index); + void storeSettings(); + void loadSettings(); boost::signal<void (const HighlightAction&)> onHighlight; private: void handleSettingChanged(const std::string& settingPath); std::string rulesToString() const; - static std::vector<HighlightRule> rulesFromString(const std::string&); static std::vector<HighlightRule> getDefaultRules(); SettingsProvider* settings_; bool storingSettings_; - void storeSettings(); - void loadSettings(); - std::vector<HighlightRule> rules_; + boost::shared_ptr<HighlightManager::HighlightRulesList> rules_; }; + typedef boost::shared_ptr<const HighlightManager::HighlightRulesList> HighlightRulesListPtr; + } diff --git a/Swift/Controllers/HighlightRule.cpp b/Swift/Controllers/HighlightRule.cpp index 9ca7d86..f1a5235 100644 --- a/Swift/Controllers/HighlightRule.cpp +++ b/Swift/Controllers/HighlightRule.cpp @@ -1,158 +1,106 @@ /* * Copyright (c) 2012 Maciej Niedzielski * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #include <algorithm> #include <boost/algorithm/string.hpp> #include <boost/lambda/lambda.hpp> #include <Swiften/Base/foreach.h> #include <Swiften/Base/Regex.h> #include <Swift/Controllers/HighlightRule.h> namespace Swift { HighlightRule::HighlightRule() : nickIsKeyword_(false) , matchCase_(false) , matchWholeWords_(false) , matchChat_(false) , matchMUC_(false) { } boost::regex HighlightRule::regexFromString(const std::string & s) const { std::string escaped = Regex::escape(s); std::string word = matchWholeWords_ ? "\\b" : ""; boost::regex::flag_type flags = boost::regex::normal; if (!matchCase_) { flags |= boost::regex::icase; } return boost::regex(word + escaped + word, flags); } void HighlightRule::updateRegex() const { keywordRegex_.clear(); foreach (const std::string & k, keywords_) { keywordRegex_.push_back(regexFromString(k)); } senderRegex_.clear(); foreach (const std::string & s, senders_) { senderRegex_.push_back(regexFromString(s)); } } std::string HighlightRule::boolToString(bool b) { return b ? "1" : "0"; } bool HighlightRule::boolFromString(const std::string& s) { return s == "1"; } -std::string HighlightRule::toString() const -{ - std::vector<std::string> v; - v.push_back(boost::join(senders_, "\t")); - v.push_back(boost::join(keywords_, "\t")); - v.push_back(boolToString(nickIsKeyword_)); - v.push_back(boolToString(matchChat_)); - v.push_back(boolToString(matchMUC_)); - v.push_back(boolToString(matchCase_)); - v.push_back(boolToString(matchWholeWords_)); - v.push_back(boolToString(action_.highlightText())); - v.push_back(action_.getTextColor()); - v.push_back(action_.getTextBackground()); - v.push_back(boolToString(action_.playSound())); - v.push_back(action_.getSoundFile()); - return boost::join(v, "\n"); -} - -HighlightRule HighlightRule::fromString(const std::string& s) -{ - std::vector<std::string> v; - boost::split(v, s, boost::is_any_of("\n")); - - HighlightRule r; - size_t i = 0; - try { - boost::split(r.senders_, v.at(i++), boost::is_any_of("\t")); - r.senders_.erase(std::remove_if(r.senders_.begin(), r.senders_.end(), boost::lambda::_1 == ""), r.senders_.end()); - boost::split(r.keywords_, v.at(i++), boost::is_any_of("\t")); - r.keywords_.erase(std::remove_if(r.keywords_.begin(), r.keywords_.end(), boost::lambda::_1 == ""), r.keywords_.end()); - r.nickIsKeyword_ = boolFromString(v.at(i++)); - r.matchChat_ = boolFromString(v.at(i++)); - r.matchMUC_ = boolFromString(v.at(i++)); - r.matchCase_ = boolFromString(v.at(i++)); - r.matchWholeWords_ = boolFromString(v.at(i++)); - r.action_.setHighlightText(boolFromString(v.at(i++))); - r.action_.setTextColor(v.at(i++)); - r.action_.setTextBackground(v.at(i++)); - r.action_.setPlaySound(boolFromString(v.at(i++))); - r.action_.setSoundFile(v.at(i++)); - } - catch (std::out_of_range) { - // this may happen if more properties are added to HighlightRule object, etc... - // in such case, default values (set by default constructor) will be used - } - - r.updateRegex(); - - return r; -} - bool HighlightRule::isMatch(const std::string& body, const std::string& sender, const std::string& nick, MessageType messageType) const { if ((messageType == HighlightRule::ChatMessage && matchChat_) || (messageType == HighlightRule::MUCMessage && matchMUC_)) { bool matchesKeyword = keywords_.empty() && (nick.empty() || !nickIsKeyword_); bool matchesSender = senders_.empty(); - foreach (const boost::regex & rx, keywordRegex_) { - if (boost::regex_search(body, rx)) { - matchesKeyword = true; - break; - } - } - if (!matchesKeyword && nickIsKeyword_ && !nick.empty()) { if (boost::regex_search(body, regexFromString(nick))) { matchesKeyword = true; } } foreach (const boost::regex & rx, senderRegex_) { if (boost::regex_search(sender, rx)) { matchesSender = true; break; } } if (matchesKeyword && matchesSender) { return true; } } return false; } void HighlightRule::setSenders(const std::vector<std::string>& senders) { senders_ = senders; updateRegex(); } void HighlightRule::setKeywords(const std::vector<std::string>& keywords) { keywords_ = keywords; updateRegex(); } void HighlightRule::setNickIsKeyword(bool nickIsKeyword) { diff --git a/Swift/Controllers/HighlightRule.h b/Swift/Controllers/HighlightRule.h index 1abfa5a..ae1a3d3 100644 --- a/Swift/Controllers/HighlightRule.h +++ b/Swift/Controllers/HighlightRule.h @@ -1,77 +1,101 @@ /* * Copyright (c) 2012 Maciej Niedzielski * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #pragma once #include <vector> #include <string> #include <boost/regex.hpp> +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.hpp> #include <Swift/Controllers/HighlightAction.h> namespace Swift { class HighlightRule { public: HighlightRule(); enum MessageType { ChatMessage, MUCMessage }; bool isMatch(const std::string& body, const std::string& sender, const std::string& nick, MessageType) const; const HighlightAction& getAction() const { return action_; } HighlightAction& getAction() { return action_; } - static HighlightRule fromString(const std::string&); - std::string toString() const; - const std::vector<std::string>& getSenders() const { return senders_; } void setSenders(const std::vector<std::string>&); + const std::vector<boost::regex>& getSenderRegex() const { return senderRegex_; } const std::vector<std::string>& getKeywords() const { return keywords_; } void setKeywords(const std::vector<std::string>&); + const std::vector<boost::regex>& getKeywordRegex() const { return keywordRegex_; } bool getNickIsKeyword() const { return nickIsKeyword_; } void setNickIsKeyword(bool); bool getMatchCase() const { return matchCase_; } void setMatchCase(bool); bool getMatchWholeWords() const { return matchWholeWords_; } void setMatchWholeWords(bool); bool getMatchChat() const { return matchChat_; } void setMatchChat(bool); bool getMatchMUC() const { return matchMUC_; } void setMatchMUC(bool); bool isEmpty() const; private: + friend class boost::serialization::access; + template<class Archive> void serialize(Archive & ar, const unsigned int version); + static std::string boolToString(bool); static bool boolFromString(const std::string&); std::vector<std::string> senders_; std::vector<std::string> keywords_; bool nickIsKeyword_; mutable std::vector<boost::regex> senderRegex_; mutable std::vector<boost::regex> keywordRegex_; void updateRegex() const; boost::regex regexFromString(const std::string&) const; bool matchCase_; bool matchWholeWords_; bool matchChat_; bool matchMUC_; HighlightAction action_; }; + template<class Archive> + void HighlightRule::serialize(Archive& ar, const unsigned int /*version*/) + { + ar & senders_; + ar & keywords_; + ar & nickIsKeyword_; + ar & matchChat_; + ar & matchMUC_; + ar & matchCase_; + ar & matchWholeWords_; + ar & action_; + updateRegex(); + } + } diff --git a/Swift/Controllers/Highlighter.cpp b/Swift/Controllers/Highlighter.cpp index 754641a..efeeb6b 100644 --- a/Swift/Controllers/Highlighter.cpp +++ b/Swift/Controllers/Highlighter.cpp @@ -1,41 +1,49 @@ /* * Copyright (c) 2012 Maciej Niedzielski * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #include <Swiften/Base/foreach.h> #include <Swift/Controllers/Highlighter.h> #include <Swift/Controllers/HighlightManager.h> namespace Swift { Highlighter::Highlighter(HighlightManager* manager) : manager_(manager) { setMode(ChatMode); } void Highlighter::setMode(Mode mode) { mode_ = mode; messageType_ = mode_ == ChatMode ? HighlightRule::ChatMessage : HighlightRule::MUCMessage; } HighlightAction Highlighter::findAction(const std::string& body, const std::string& sender) const { - foreach (const HighlightRule & r, manager_->getRules()) { - if (r.isMatch(body, sender, nick_, messageType_)) { - return r.getAction(); + HighlightRulesListPtr rules = manager_->getRules(); + for (size_t i = 0; i < rules->getSize(); ++i) { + const HighlightRule& rule = rules->getRule(i); + if (rule.isMatch(body, sender, nick_, messageType_)) { + return rule.getAction(); } } return HighlightAction(); } void Highlighter::handleHighlightAction(const HighlightAction& action) { manager_->onHighlight(action); } } diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 79b7502..a16cbe7 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -1,43 +1,37 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -/* - * Copyright (c) 2013 Remko Tronçon + * Copyright (c) 2010-2014 Kevin Smith and Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swift/Controllers/MainController.h> #include <cstdlib> #include <boost/bind.hpp> #include <boost/lexical_cast.hpp> #include <boost/shared_ptr.hpp> #include <boost/smart_ptr/make_shared.hpp> #include <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> @@ -337,70 +331,71 @@ void MainController::handleConnected() { if (freshLogin) { profileController_ = new ProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_); showProfileController_ = new ShowProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_); ftOverview_ = new FileTransferOverview(client_->getFileTransferManager()); fileTransferListController_->setFileTransferOverview(ftOverview_); rosterController_ = new RosterController(boundJID_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_, client_->getClientBlockListManager(), client_->getVCardManager()); rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2)); rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this)); rosterController_->getWindow()->onShowCertificateRequest.connect(boost::bind(&MainController::handleShowCertificateRequest, this)); blockListController_ = new BlockListController(client_->getClientBlockListManager(), uiEventStream_, uiFactory_, eventController_); contactEditController_ = new ContactEditController(rosterController_, client_->getVCardManager(), uiFactory_, uiEventStream_); whiteboardManager_ = new WhiteboardManager(uiFactory_, uiEventStream_, client_->getNickResolver(), client_->getWhiteboardSessionManager()); /* Doing this early as an ordering fix. Various things later will * want to have the user's nick available and this means it will * be before they receive stanzas that need it (e.g. bookmarks).*/ client_->getVCardManager()->requestOwnVCard(); contactSuggesterWithoutRoster_ = new ContactSuggester(); contactSuggesterWithRoster_ = new ContactSuggester(); userSearchControllerInvite_ = new UserSearchController(UserSearchController::InviteToChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithRoster_, client_->getAvatarManager(), client_->getPresenceOracle()); #ifdef SWIFT_EXPERIMENTAL_HISTORY historyController_ = new HistoryController(storages_->getHistoryStorage()); historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_); chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_, client_->getVCardManager()); #else chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_, client_->getVCardManager()); #endif contactsFromRosterProvider_ = new ContactsFromXMPPRoster(client_->getRoster(), client_->getAvatarManager(), client_->getPresenceOracle()); contactSuggesterWithoutRoster_->addContactProvider(chatsManager_); contactSuggesterWithRoster_->addContactProvider(chatsManager_); contactSuggesterWithRoster_->addContactProvider(contactsFromRosterProvider_); + highlightEditorController_->setContactSuggester(contactSuggesterWithoutRoster_); client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); chatsManager_->setAvatarManager(client_->getAvatarManager()); eventWindowController_ = new EventWindowController(eventController_, uiFactory_); loginWindow_->morphInto(rosterController_->getWindow()); DiscoInfo discoInfo; discoInfo.addIdentity(DiscoInfo::Identity(CLIENT_NAME, "client", "pc")); discoInfo.addFeature(DiscoInfo::ChatStatesFeature); discoInfo.addFeature(DiscoInfo::SecurityLabelsFeature); discoInfo.addFeature(DiscoInfo::MessageCorrectionFeature); #ifdef SWIFT_EXPERIMENTAL_FT discoInfo.addFeature(DiscoInfo::JingleFeature); discoInfo.addFeature(DiscoInfo::JingleFTFeature); discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature); discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature); #endif #ifdef SWIFT_EXPERIMENTAL_WB discoInfo.addFeature(DiscoInfo::WhiteboardFeature); #endif discoInfo.addFeature(DiscoInfo::MessageDeliveryReceiptsFeature); client_->getDiscoManager()->setCapsNode(CLIENT_NODE); client_->getDiscoManager()->setDiscoInfo(discoInfo); userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithRoster_, client_->getAvatarManager(), client_->getPresenceOracle()); userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithoutRoster_, client_->getAvatarManager(), client_->getPresenceOracle()); adHocManager_ = new AdHocManager(JID(boundJID_.getDomain()), uiFactory_, client_->getIQRouter(), uiEventStream_, rosterController_->getWindow()); chatsManager_->onImpromptuMUCServiceDiscovered.connect(boost::bind(&UserSearchController::setCanInitiateImpromptuMUC, userSearchControllerChat_, _1)); } loginWindow_->setIsLoggingIn(false); client_->requestRoster(); diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 4c71268..5ebbdd3 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -30,70 +30,71 @@ if env["SCONS_STAGE"] == "build" : "Chat/UserSearchController.cpp", "Chat/ChatMessageParser.cpp", "ContactSuggester.cpp", "MainController.cpp", "ProfileController.cpp", "ShowProfileController.cpp", "ContactEditController.cpp", "FileTransfer/FileTransferController.cpp", "FileTransfer/FileTransferOverview.cpp", "FileTransfer/FileTransferProgressInfo.cpp", "Roster/RosterController.cpp", "Roster/RosterGroupExpandinessPersister.cpp", "Roster/ContactRosterItem.cpp", "Roster/GroupRosterItem.cpp", "Roster/RosterItem.cpp", "Roster/Roster.cpp", "Roster/RosterVCardProvider.cpp", "Roster/TableRoster.cpp", "EventWindowController.cpp", "SoundEventController.cpp", "SystemTrayController.cpp", "XMLConsoleController.cpp", "HistoryViewController.cpp", "HistoryController.cpp", "FileTransferListController.cpp", "BlockListController.cpp", "StatusTracker.cpp", "PresenceNotifier.cpp", "EventNotifier.cpp", "AdHocManager.cpp", "AdHocController.cpp", "XMPPEvents/EventController.cpp", "UIEvents/UIEvent.cpp", "UIInterfaces/XMLConsoleWidget.cpp", "UIInterfaces/ChatListWindow.cpp", + "UIInterfaces/HighlightEditorWindow.cpp", "PreviousStatusStore.cpp", "ProfileSettingsProvider.cpp", "Settings/SettingsProviderHierachy.cpp", "Settings/XMLSettingsProvider.cpp", "Storages/CertificateStorageFactory.cpp", "Storages/CertificateStorage.cpp", "Storages/CertificateFileStorage.cpp", "Storages/CertificateMemoryStorage.cpp", "Storages/AvatarFileStorage.cpp", "Storages/FileStorages.cpp", "Storages/RosterFileStorage.cpp", "Storages/CapsFileStorage.cpp", "Storages/VCardFileStorage.cpp", "StatusUtil.cpp", "Translator.cpp", "XMPPURIController.cpp", "ChatMessageSummarizer.cpp", "SettingConstants.cpp", "WhiteboardManager.cpp", "StatusCache.cpp", "HighlightAction.cpp", "HighlightEditorController.cpp", "HighlightManager.cpp", "HighlightRule.cpp", "Highlighter.cpp", "ContactsFromXMPPRoster.cpp", "ContactProvider.cpp", "Contact.cpp" ]) env.Append(UNITTEST_SOURCES = [ File("Roster/UnitTest/RosterControllerTest.cpp"), File("Roster/UnitTest/RosterTest.cpp"), File("Roster/UnitTest/LeastCommonSubsequenceTest.cpp"), File("Roster/UnitTest/TableRosterTest.cpp"), diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp new file mode 100644 index 0000000..f90903b --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h> + +namespace Swift { + +HighlightEditorWindow::HighlightEditorWindow() +{ +} + +HighlightEditorWindow::~HighlightEditorWindow() +{ +} + +} diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h new file mode 100644 index 0000000..83ae959 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <vector> +#include <Swiften/Base/boost_bsignals.h> +#include <Swift/Controllers/Contact.h> + +namespace Swift { + +class HighlightManager; + +class HighlightEditorWindow { +public: + HighlightEditorWindow(); + virtual ~HighlightEditorWindow(); + virtual void show() = 0; + virtual void setHighlightManager(HighlightManager *highlightManager) = 0; + virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions) = 0; + +public: + boost::signal<void (const std::string&)> onContactSuggestionsRequested; +}; + +} diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h b/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h new file mode 100644 index 0000000..e0aaaef --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +namespace Swift { + class HighlightEditorWindow; + + class HighlightEditorWindowFactory { + public : + virtual ~HighlightEditorWindowFactory() {} + + virtual HighlightEditorWindow* createHighlightEditorWindow() = 0; + }; +} diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h index 990dc98..54fa7ce 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -1,49 +1,49 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/UIInterfaces/HistoryWindowFactory.h> #include <Swift/Controllers/UIInterfaces/EventWindowFactory.h> #include <Swift/Controllers/UIInterfaces/LoginWindowFactory.h> #include <Swift/Controllers/UIInterfaces/MainWindowFactory.h> #include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h> #include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h> #include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h> #include <Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h> #include <Swift/Controllers/UIInterfaces/ProfileWindowFactory.h> #include <Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h> #include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> #include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h> #include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h> -#include <Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h> #include <Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h> namespace Swift { class UIFactory : public ChatListWindowFactory, public ChatWindowFactory, public HistoryWindowFactory, public EventWindowFactory, public LoginWindowFactory, public MainWindowFactory, public MUCSearchWindowFactory, public XMLConsoleWidgetFactory, public UserSearchWindowFactory, public JoinMUCWindowFactory, public ProfileWindowFactory, public ContactEditWindowFactory, public AdHocCommandWindowFactory, public FileTransferListWidgetFactory, public WhiteboardWindowFactory, - public HighlightEditorWidgetFactory, + public HighlightEditorWindowFactory, public BlockListEditorWidgetFactory { public: virtual ~UIFactory() {} }; } diff --git a/Swift/Controllers/UnitTest/HighlightRuleTest.cpp b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp index ec81227..c988b8d 100644 --- a/Swift/Controllers/UnitTest/HighlightRuleTest.cpp +++ b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp @@ -1,72 +1,78 @@ /* * Copyright (c) 2012 Maciej Niedzielski * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #include <vector> #include <string> #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <Swift/Controllers/HighlightRule.h> using namespace Swift; class HighlightRuleTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(HighlightRuleTest); CPPUNIT_TEST(testEmptyRuleNeverMatches); CPPUNIT_TEST(testKeyword); CPPUNIT_TEST(testNickKeyword); CPPUNIT_TEST(testNickWithoutOtherKeywords); CPPUNIT_TEST(testSender); CPPUNIT_TEST(testSenderAndKeyword); CPPUNIT_TEST(testWholeWords); CPPUNIT_TEST(testCase); CPPUNIT_TEST(testWholeWordsAndCase); CPPUNIT_TEST(testMUC); CPPUNIT_TEST_SUITE_END(); public: void setUp() { std::vector<std::string> keywords; keywords.push_back("keyword1"); keywords.push_back("KEYWORD2"); - std::vector<std::string>senders; + std::vector<std::string> senders; senders.push_back("sender1"); senders.push_back("SENDER2"); emptyRule = new HighlightRule(); keywordRule = new HighlightRule(); keywordRule->setKeywords(keywords); keywordChatRule = new HighlightRule(); keywordChatRule->setKeywords(keywords); keywordChatRule->setMatchChat(true); keywordNickChatRule = new HighlightRule(); keywordNickChatRule->setKeywords(keywords); keywordNickChatRule->setNickIsKeyword(true); keywordNickChatRule->setMatchChat(true); nickChatRule = new HighlightRule(); nickChatRule->setNickIsKeyword(true); nickChatRule->setMatchChat(true); nickRule = new HighlightRule(); nickRule->setNickIsKeyword(true); senderRule = new HighlightRule(); senderRule->setSenders(senders); senderChatRule = new HighlightRule(); senderChatRule->setSenders(senders); senderChatRule->setMatchChat(true); senderKeywordChatRule = new HighlightRule(); senderKeywordChatRule->setSenders(senders); senderKeywordChatRule->setKeywords(keywords); senderKeywordChatRule->setMatchChat(true); @@ -125,194 +131,194 @@ class HighlightRuleTest : public CppUnit::TestFixture { delete senderKeywordNickCaseWordChatRule; delete senderKeywordNickMUCRule; } void testEmptyRuleNeverMatches() { CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "nick", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "nick", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "nick", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "", HighlightRule::MUCMessage), false); } void testKeyword() { CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false); - CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(keywordRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "sender contains keyword1", "nick", HighlightRule::ChatMessage), false); - CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc keyword1 xyz", "from", "nick", HighlightRule::ChatMessage), true); - CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abckeyword1xyz", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc keyword1 xyz", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abckeyword1xyz", "from", "nick", HighlightRule::ChatMessage), false); - CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("KEYword1", "from", "nick", HighlightRule::ChatMessage), true); - CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc KEYword1 xyz", "from", "nick", HighlightRule::ChatMessage), true); - CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abcKEYword1xyz", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("KEYword1", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc KEYword1 xyz", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abcKEYword1xyz", "from", "nick", HighlightRule::ChatMessage), false); - CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword2", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword2", "from", "nick", HighlightRule::ChatMessage), false); } void testNickKeyword() { CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), false); - CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body", "sender contains nick", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains mixed-case NiCk", "sender", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("nickname", "from", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("NIckNAME", "from", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body", "from", "", HighlightRule::ChatMessage), false); } void testNickWithoutOtherKeywords() { CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(nickRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body", "sender contains nick but it does't matter", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body contains mixed-case NiCk", "from", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("nickname", "from", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("NIckNAME", "from", "nick", HighlightRule::ChatMessage), true); // there are no keywords in this rule and empty nick is not treated as a keyword, so we don't check for keywords to get a match CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body", "from", "", HighlightRule::ChatMessage), true); } void testSender() { CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "sender1", "nick", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(senderRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderRule->isMatch("body contains sender1", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abc sender1 xyz", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abcsender1xyz", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "SENDer1", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abc SENDer1 xyz", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abcSENDer1xyz", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "sender2", "nick", HighlightRule::ChatMessage), true); } void testSenderAndKeyword() { CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); - CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); } void testWholeWords() { CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); - CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("xkeyword1", "sender1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "xsender1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), false); - CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), true); } void testCase() { CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); - CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true); - CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("xkeyword1", "xsender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("xkeyword1", "xsender1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("KEYword1", "sender1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), false); } void testWholeWordsAndCase() { CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); - CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("xkeyword1", "sender1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "xsender1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("KEYword1", "sender1", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), false); } void testMUC() { CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); - CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::MUCMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::MUCMessage), false); CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::MUCMessage), true); } private: HighlightRule* emptyRule; HighlightRule* keywordRule; HighlightRule* keywordChatRule; HighlightRule* keywordNickChatRule; HighlightRule* nickChatRule; HighlightRule* nickRule; HighlightRule* senderRule; HighlightRule* senderChatRule; HighlightRule* senderKeywordChatRule; HighlightRule* senderKeywordNickChatRule; HighlightRule* senderKeywordNickWordChatRule; HighlightRule* senderKeywordNickCaseChatRule; HighlightRule* senderKeywordNickCaseWordChatRule; HighlightRule* senderKeywordNickMUCRule; }; CPPUNIT_TEST_SUITE_REGISTRATION(HighlightRuleTest); diff --git a/Swift/QtUI/QtHighlightEditor.cpp b/Swift/QtUI/QtHighlightEditor.cpp new file mode 100644 index 0000000..3900cf9 --- /dev/null +++ b/Swift/QtUI/QtHighlightEditor.cpp @@ -0,0 +1,524 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cassert> + +#include <boost/lexical_cast.hpp> + +#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h> +#include <Swift/Controllers/HighlightManager.cpp> +#include <Swift/QtUI/QtHighlightEditor.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtSettingsProvider.h> + +#include <QTreeWidgetItem> +#include <QFileDialog> + +namespace Swift { + +QtHighlightEditor::QtHighlightEditor(QtSettingsProvider* settings, QWidget* parent) + : QWidget(parent), settings_(settings), previousRow_(-1) +{ + ui_.setupUi(this); + + connect(ui_.listWidget, SIGNAL(currentRowChanged(int)), SLOT(onCurrentRowChanged(int))); + + connect(ui_.newButton, SIGNAL(clicked()), SLOT(onNewButtonClicked())); + connect(ui_.deleteButton, SIGNAL(clicked()), SLOT(onDeleteButtonClicked())); + + connect(ui_.buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(onApplyButtonClick())); + connect(ui_.buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), SLOT(onCancelButtonClick())); + connect(ui_.buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(onOkButtonClick())); + + connect(ui_.noColorRadio, SIGNAL(clicked()), SLOT(colorOtherSelect())); + connect(ui_.defaultColorRadio, SIGNAL(clicked()), SLOT(colorOtherSelect())); + connect(ui_.customColorRadio, SIGNAL(clicked()), SLOT(colorCustomSelect())); + + connect(ui_.noSoundRadio, SIGNAL(clicked()), SLOT(soundOtherSelect())); + connect(ui_.defaultSoundRadio, SIGNAL(clicked()), SLOT(soundOtherSelect())); + connect(ui_.customSoundRadio, SIGNAL(clicked()), SLOT(soundCustomSelect())); + + /* replace the static line-edit control with the roster autocompleter */ + ui_.dummySenderName->setVisible(false); + jid_ = new QtSuggestingJIDInput(this, settings); + ui_.senderName->addWidget(jid_); + jid_->onUserSelected.connect(boost::bind(&QtHighlightEditor::handleOnUserSelected, this, _1)); + + /* handle autocomplete */ + connect(jid_, SIGNAL(textEdited(QString)), SLOT(handleContactSuggestionRequested(QString))); + + /* we need to be notified if any of the state changes so that we can update our textual rule description */ + connect(ui_.chatRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.roomRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.nickIsKeyword, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.allMsgRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.senderRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(jid_, SIGNAL(textChanged(const QString&)), SLOT(widgetClick())); + connect(ui_.keywordRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.keyword, SIGNAL(textChanged(const QString&)), SLOT(widgetClick())); + connect(ui_.matchPartialWords, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.matchCase, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.noColorRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.defaultColorRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.customColorRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.noSoundRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.defaultSoundRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.customSoundRadio, SIGNAL(clicked()), SLOT(widgetClick())); + + /* allow selection of a custom sound file */ + connect(ui_.soundFileButton, SIGNAL(clicked()), SLOT(selectSoundFile())); + + /* if these are not needed, then they should be removed */ + ui_.moveUpButton->setVisible(false); + ui_.moveDownButton->setVisible(false); + + setWindowTitle(tr("Highlight Rules")); +} + +QtHighlightEditor::~QtHighlightEditor() +{ +} + +std::string formatShortDescription(const HighlightRule &rule) +{ + const std::string chatOrRoom = (rule.getMatchChat() ? "chat" : "room"); + + std::vector<std::string> senders = rule.getSenders(); + std::vector<std::string> keywords = rule.getKeywords(); + + if (senders.empty() && keywords.empty() && !rule.getNickIsKeyword()) { + return std::string("All ") + chatOrRoom + " messages."; + } + + if (rule.getNickIsKeyword()) { + return std::string("All ") + chatOrRoom + " messages that mention my name."; + } + + if (!senders.empty()) { + return std::string("All ") + chatOrRoom + " messages from " + senders[0] + "."; + } + + if (!keywords.empty()) { + return std::string("All ") + chatOrRoom + " messages mentioning the keyword '" + keywords[0] + "'."; + } + + return "Unknown Rule"; +} + +void QtHighlightEditor::show() +{ + highlightManager_->loadSettings(); + + populateList(); + + if (ui_.listWidget->count()) { + selectRow(0); + } + + /* prepare default states */ + widgetClick(); + + QWidget::show(); + QWidget::activateWindow(); +} + +void QtHighlightEditor::setHighlightManager(HighlightManager* highlightManager) +{ + highlightManager_ = highlightManager; +} + +void QtHighlightEditor::setContactSuggestions(const std::vector<Contact::ref>& suggestions) +{ + jid_->setSuggestions(suggestions); +} + +void QtHighlightEditor::colorOtherSelect() +{ + ui_.foregroundColor->setEnabled(false); + ui_.backgroundColor->setEnabled(false); +} + +void QtHighlightEditor::colorCustomSelect() +{ + ui_.foregroundColor->setEnabled(true); + ui_.backgroundColor->setEnabled(true); +} + +void QtHighlightEditor::soundOtherSelect() +{ + ui_.soundFile->setEnabled(false); + ui_.soundFileButton->setEnabled(false); +} + +void QtHighlightEditor::soundCustomSelect() +{ + ui_.soundFile->setEnabled(true); + ui_.soundFileButton->setEnabled(true); +} + +void QtHighlightEditor::onNewButtonClicked() +{ + int row = getSelectedRow() + 1; + populateList(); + HighlightRule newRule; + newRule.setMatchChat(true); + highlightManager_->insertRule(row, newRule); + QListWidgetItem *item = new QListWidgetItem(); + item->setText(P2QSTRING(formatShortDescription(newRule))); + ui_.listWidget->insertItem(row, item); + selectRow(row); +} + +void QtHighlightEditor::onDeleteButtonClicked() +{ + int selectedRow = getSelectedRow(); + assert(selectedRow>=0 && selectedRow<ui_.listWidget->count()); + delete ui_.listWidget->takeItem(selectedRow); + highlightManager_->removeRule(selectedRow); + + if (!ui_.listWidget->count()) { + disableDialog(); + ui_.deleteButton->setEnabled(false); + } else { + if (selectedRow == ui_.listWidget->count()) { + selectRow(ui_.listWidget->count() - 1); + } else { + selectRow(selectedRow); + } + } +} + +void QtHighlightEditor::onCurrentRowChanged(int currentRow) +{ + ui_.deleteButton->setEnabled(currentRow != -1); + ui_.moveUpButton->setEnabled(currentRow != -1 && currentRow != 0); + ui_.moveDownButton->setEnabled(currentRow != -1 && currentRow != (ui_.listWidget->count()-1)); + + if (previousRow_ != -1) { + if (ui_.listWidget->count() > previousRow_) { + highlightManager_->setRule(previousRow_, ruleFromDialog()); + } + } + + if (currentRow != -1) { + HighlightRule rule = highlightManager_->getRule(currentRow); + ruleToDialog(rule); + if (ui_.listWidget->currentItem()) { + ui_.listWidget->currentItem()->setText(P2QSTRING(formatShortDescription(rule))); + } + } + + /* grey the dialog if we have nothing selected */ + if (currentRow == -1) { + disableDialog(); + } + + previousRow_ = currentRow; +} + +void QtHighlightEditor::onApplyButtonClick() +{ + selectRow(getSelectedRow()); /* force save */ + highlightManager_->storeSettings(); +} + +void QtHighlightEditor::onCancelButtonClick() +{ + close(); +} + +void QtHighlightEditor::onOkButtonClick() +{ + onApplyButtonClick(); + close(); +} + +void QtHighlightEditor::setChildWidgetStates() +{ + /* disable appropriate radio button child widgets */ + + if (ui_.chatRadio->isChecked()) { + if (ui_.nickIsKeyword->isChecked()) { + /* switch to another choice before we disable this button */ + ui_.allMsgRadio->setChecked(true); + } + ui_.nickIsKeyword->setEnabled(false); + } else if (ui_.roomRadio->isChecked()) { + ui_.nickIsKeyword->setEnabled(true); + } else { /* chats and rooms */ + ui_.nickIsKeyword->setEnabled(true); + } + + if (ui_.senderRadio->isChecked()) { + jid_->setEnabled(true); + } else { + jid_->setEnabled(false); + } + + if (ui_.keywordRadio->isChecked()) { + ui_.keyword->setEnabled(true); + ui_.matchPartialWords->setEnabled(true); + ui_.matchCase->setEnabled(true); + } else { + ui_.keyword->setEnabled(false); + ui_.matchPartialWords->setEnabled(false); + ui_.matchCase->setEnabled(false); + } + + if (ui_.chatRadio->isChecked()) { + ui_.allMsgRadio->setText(tr("Apply to all chat messages")); + } else { + ui_.allMsgRadio->setText(tr("Apply to all room messages")); + } +} + +void QtHighlightEditor::widgetClick() +{ + setChildWidgetStates(); + + HighlightRule rule = ruleFromDialog(); + + if (ui_.listWidget->currentItem()) { + ui_.listWidget->currentItem()->setText(P2QSTRING(formatShortDescription(rule))); + } +} + +void QtHighlightEditor::disableDialog() +{ + ui_.chatRadio->setEnabled(false); + ui_.roomRadio->setEnabled(false); + ui_.allMsgRadio->setEnabled(false); + ui_.nickIsKeyword->setEnabled(false); + ui_.senderRadio->setEnabled(false); + ui_.dummySenderName->setEnabled(false); + ui_.keywordRadio->setEnabled(false); + ui_.keyword->setEnabled(false); + ui_.matchPartialWords->setEnabled(false); + ui_.matchCase->setEnabled(false); + ui_.noColorRadio->setEnabled(false); + ui_.defaultColorRadio->setEnabled(false); + ui_.customColorRadio->setEnabled(false); + ui_.foregroundColor->setEnabled(false); + ui_.backgroundColor->setEnabled(false); + ui_.noSoundRadio->setEnabled(false); + ui_.defaultSoundRadio->setEnabled(false); + ui_.customSoundRadio->setEnabled(false); + ui_.soundFile->setEnabled(false); + ui_.soundFileButton->setEnabled(false); +} + +void QtHighlightEditor::handleContactSuggestionRequested(const QString& text) +{ + std::string stdText = Q2PSTRING(text); + onContactSuggestionsRequested(stdText); +} + +void QtHighlightEditor::selectSoundFile() +{ + QString path = QFileDialog::getOpenFileName(this, tr("Select sound file..."), QString(), "Sounds (*.wav)"); + ui_.soundFile->setText(path); +} + +void QtHighlightEditor::handleOnUserSelected(const JID& jid) { + /* this might seem like it should be standard behaviour for the suggesting input box, but is not desirable in all cases */ + jid_->setText(P2QSTRING(jid.toString())); +} + +void QtHighlightEditor::populateList() +{ + previousRow_ = -1; + ui_.listWidget->clear(); + HighlightRulesListPtr rules = highlightManager_->getRules(); + for (size_t i = 0; i < rules->getSize(); ++i) { + const HighlightRule& rule = rules->getRule(i); + QListWidgetItem *item = new QListWidgetItem(); + item->setText(P2QSTRING(formatShortDescription(rule))); + ui_.listWidget->addItem(item); + } +} + +void QtHighlightEditor::selectRow(int row) +{ + for (int i = 0; i < ui_.listWidget->count(); ++i) { + if (i == row) { + ui_.listWidget->item(i)->setSelected(true); + onCurrentRowChanged(i); + } else { + ui_.listWidget->item(i)->setSelected(false); + } + } + ui_.listWidget->setCurrentRow(row); +} + +int QtHighlightEditor::getSelectedRow() const +{ + for (int i = 0; i < ui_.listWidget->count(); ++i) { + if (ui_.listWidget->item(i)->isSelected()) { + return i; + } + } + return -1; +} + +HighlightRule QtHighlightEditor::ruleFromDialog() +{ + HighlightRule rule; + + if (ui_.chatRadio->isChecked()) { + rule.setMatchChat(true); + rule.setMatchMUC(false); + } else { + rule.setMatchChat(false); + rule.setMatchMUC(true); + } + + if (ui_.senderRadio->isChecked()) { + QString senderName = jid_->text(); + if (!senderName.isEmpty()) { + std::vector<std::string> senders; + senders.push_back(Q2PSTRING(senderName)); + rule.setSenders(senders); + } + } + + if (ui_.keywordRadio->isChecked()) { + QString keywordString = ui_.keyword->text(); + if (!keywordString.isEmpty()) { + std::vector<std::string> keywords; + keywords.push_back(Q2PSTRING(keywordString)); + rule.setKeywords(keywords); + } + } + + rule.setNickIsKeyword(ui_.nickIsKeyword->isChecked()); + rule.setMatchWholeWords(!ui_.matchPartialWords->isChecked()); + rule.setMatchCase(ui_.matchCase->isChecked()); + + HighlightAction& action = rule.getAction(); + + if (ui_.noColorRadio->isChecked()) { + action.setHighlightText(false); + action.setTextColor(""); + action.setTextBackground(""); + } else if (ui_.defaultColorRadio->isChecked()) { + action.setHighlightText(true); + action.setTextColor(""); + action.setTextBackground(""); + } else { + action.setHighlightText(true); + action.setTextColor(Q2PSTRING(ui_.foregroundColor->getColor().name())); + action.setTextBackground(Q2PSTRING(ui_.backgroundColor->getColor().name())); + } + + if (ui_.noSoundRadio->isChecked()) { + action.setPlaySound(false); + } else if (ui_.defaultSoundRadio->isChecked()) { + action.setPlaySound(true); + action.setSoundFile(""); + } else { + action.setPlaySound(true); + action.setSoundFile(Q2PSTRING(ui_.soundFile->text())); + } + + return rule; +} + +void QtHighlightEditor::ruleToDialog(const HighlightRule& rule) +{ + ui_.chatRadio->setEnabled(true); + ui_.roomRadio->setEnabled(true); + + if (rule.getMatchMUC()) { + ui_.chatRadio->setChecked(false); + ui_.roomRadio->setChecked(true); + } else { + ui_.chatRadio->setChecked(true); + ui_.roomRadio->setChecked(false); + } + + ui_.allMsgRadio->setEnabled(true); + ui_.allMsgRadio->setChecked(true); /* this is the default radio button */ + jid_->setText(""); + ui_.keyword->setText(""); + ui_.matchPartialWords->setChecked(false); + ui_.matchCase->setChecked(false); + + ui_.nickIsKeyword->setEnabled(true); + if (rule.getNickIsKeyword()) { + ui_.nickIsKeyword->setChecked(true); + } + + ui_.senderRadio->setEnabled(true); + std::vector<std::string> senders = rule.getSenders(); + if (!senders.empty()) { + ui_.senderRadio->setChecked(true); + jid_->setText(P2QSTRING(senders[0])); + } + + ui_.keywordRadio->setEnabled(true); + std::vector<std::string> keywords = rule.getKeywords(); + if (!keywords.empty()) { + ui_.keywordRadio->setChecked(true); + ui_.keyword->setText(P2QSTRING(keywords[0])); + ui_.matchPartialWords->setChecked(!rule.getMatchWholeWords()); + ui_.matchCase->setChecked(rule.getMatchCase()); + } + + const HighlightAction& action = rule.getAction(); + + ui_.noColorRadio->setEnabled(true); + ui_.defaultColorRadio->setEnabled(true); + ui_.customColorRadio->setEnabled(true); + if (action.highlightText()) { + if (action.getTextColor().empty() && action.getTextBackground().empty()) { + ui_.defaultColorRadio->setChecked(true); + ui_.foregroundColor->setEnabled(false); + ui_.backgroundColor->setEnabled(false); + } else { + ui_.foregroundColor->setEnabled(true); + ui_.backgroundColor->setEnabled(true); + QColor foregroundColor(P2QSTRING(action.getTextColor())); + ui_.foregroundColor->setColor(foregroundColor); + QColor backgroundColor(P2QSTRING(action.getTextBackground())); + ui_.backgroundColor->setColor(backgroundColor); + ui_.customColorRadio->setChecked(true); + } + } else { + ui_.noColorRadio->setChecked(true); + ui_.foregroundColor->setEnabled(false); + ui_.backgroundColor->setEnabled(false); + } + + ui_.noSoundRadio->setEnabled(true); + ui_.defaultSoundRadio->setEnabled(true); + ui_.customSoundRadio->setEnabled(true); + ui_.soundFile->setText(""); + ui_.soundFile->setEnabled(false); + ui_.soundFileButton->setEnabled(false); + if (action.playSound()) { + if (action.getSoundFile().empty()) { + ui_.defaultSoundRadio->setChecked(true); + } else { + ui_.customSoundRadio->setChecked(true); + ui_.soundFile->setText(P2QSTRING(action.getSoundFile())); + ui_.soundFile->setEnabled(true); + ui_.soundFileButton->setEnabled(true); + } + } else { + ui_.noSoundRadio->setChecked(true); + } + + /* set radio button child option states */ + setChildWidgetStates(); +} + +} diff --git a/Swift/QtUI/QtHighlightEditor.h b/Swift/QtUI/QtHighlightEditor.h new file mode 100644 index 0000000..c7db464 --- /dev/null +++ b/Swift/QtUI/QtHighlightEditor.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/HighlightRule.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h> +#include <Swift/QtUI/ui_QtHighlightEditor.h> + +namespace Swift { + + class QtSettingsProvider; + class QtSuggestingJIDInput; + class QtWebKitChatView; + + class QtHighlightEditor : public QWidget, public HighlightEditorWindow { + Q_OBJECT + + public: + QtHighlightEditor(QtSettingsProvider* settings, QWidget* parent = NULL); + virtual ~QtHighlightEditor(); + + virtual void show(); + virtual void setHighlightManager(HighlightManager* highlightManager); + virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions); + + private slots: + void colorOtherSelect(); + void colorCustomSelect(); + void soundOtherSelect(); + void soundCustomSelect(); + void onNewButtonClicked(); + void onDeleteButtonClicked(); + void onCurrentRowChanged(int currentRow); + void onApplyButtonClick(); + void onCancelButtonClick(); + void onOkButtonClick(); + void setChildWidgetStates(); + void widgetClick(); + void disableDialog(); + void handleContactSuggestionRequested(const QString& text); + void selectSoundFile(); + + private: + void handleOnUserSelected(const JID& jid); + void populateList(); + void updateChatPreview(); + void selectRow(int row); + int getSelectedRow() const; + HighlightRule ruleFromDialog(); + void ruleToDialog(const HighlightRule& rule); + + Ui::QtHighlightEditor ui_; + QtSettingsProvider* settings_; + HighlightManager* highlightManager_; + QtSuggestingJIDInput* jid_; + int previousRow_; + }; + +} diff --git a/Swift/QtUI/QtHighlightEditor.ui b/Swift/QtUI/QtHighlightEditor.ui new file mode 100644 index 0000000..09a7297 --- /dev/null +++ b/Swift/QtUI/QtHighlightEditor.ui @@ -0,0 +1,446 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtHighlightEditor</class> + <widget class="QWidget" name="QtHighlightEditor"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>439</width> + <height>836</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>439</width> + <height>836</height> + </size> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Incoming messages are checked against the following rules. First rule that matches will be executed.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QListWidget" name="listWidget"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer_8"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="newButton"> + <property name="text"> + <string>New Rule</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="deleteButton"> + <property name="text"> + <string>Remove Rule</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="moveUpButton"> + <property name="text"> + <string>Move Up</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="moveDownButton"> + <property name="text"> + <string>Move Down</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Apply Rule To</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QRadioButton" name="chatRadio"> + <property name="text"> + <string>Chats</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="roomRadio"> + <property name="text"> + <string>Rooms</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>246</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_6"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Rule Conditions</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QRadioButton" name="allMsgRadio"> + <property name="text"> + <string>Apply to all messages</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="nickIsKeyword"> + <property name="text"> + <string>Only messages mentioning me</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="senderRadio"> + <property name="text"> + <string>Messages from this sender:</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="senderName"> + <property name="sizeConstraint"> + <enum>QLayout::SetMinimumSize</enum> + </property> + <item> + <widget class="QLineEdit" name="dummySenderName"/> + </item> + </layout> + </item> + <item> + <widget class="QRadioButton" name="keywordRadio"> + <property name="text"> + <string>Messages containing this keyword:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="keyword"/> + </item> + <item> + <widget class="QCheckBox" name="matchPartialWords"> + <property name="text"> + <string>Match keyword within longer words</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="matchCase"> + <property name="text"> + <string>Keyword is case sensitive</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Highlight Action</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="QRadioButton" name="noColorRadio"> + <property name="text"> + <string>No Highlight</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="defaultColorRadio"> + <property name="text"> + <string>Default Color</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="customColorRadio"> + <property name="text"> + <string>Custom Color</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="Swift::QtColorToolButton" name="foregroundColor"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Text</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextBesideIcon</enum> + </property> + </widget> + </item> + <item> + <widget class="Swift::QtColorToolButton" name="backgroundColor"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Background</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextBesideIcon</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Sound Action</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QRadioButton" name="noSoundRadio"> + <property name="text"> + <string>No Sound</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="defaultSoundRadio"> + <property name="text"> + <string>Default Sound</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="customSoundRadio"> + <property name="text"> + <string>Custom Sound</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLineEdit" name="soundFile"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="readOnly"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="soundFileButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="Line" name="line_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Swift::QtColorToolButton</class> + <extends>QToolButton</extends> + <header>QtColorToolButton.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/QtHighlightRuleWidget.cpp b/Swift/QtUI/QtHighlightRuleWidget.cpp deleted file mode 100644 index 9c0df5e..0000000 --- a/Swift/QtUI/QtHighlightRuleWidget.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2012 Maciej Niedzielski - * Licensed under the simplified BSD license. - * See Documentation/Licenses/BSD-simplified.txt for more information. - */ - -#include <QDataWidgetMapper> -#include <QStringListModel> -#include <QFileDialog> - -#include <Swift/QtUI/QtHighlightRuleWidget.h> -#include <Swift/QtUI/QtHighlightRulesItemModel.h> - -namespace Swift { - -QtHighlightRuleWidget::QtHighlightRuleWidget(QWidget* parent) - : QWidget(parent) -{ - ui_.setupUi(this); - - QStringList applyToItems; - for (int i = 0; i < QtHighlightRulesItemModel::ApplyToEOL; ++i) { - applyToItems << QtHighlightRulesItemModel::getApplyToString(i); - } - QStringListModel * applyToModel = new QStringListModel(applyToItems, this); - ui_.applyTo->setModel(applyToModel); - - connect(ui_.highlightText, SIGNAL(toggled(bool)), SLOT(onHighlightTextToggled(bool))); - connect(ui_.customColors, SIGNAL(toggled(bool)), SLOT(onCustomColorsToggled(bool))); - connect(ui_.playSound, SIGNAL(toggled(bool)), SLOT(onPlaySoundToggled(bool))); - connect(ui_.customSound, SIGNAL(toggled(bool)), SLOT(onCustomSoundToggled(bool))); - connect(ui_.soundFileButton, SIGNAL(clicked()), SLOT(onSoundFileButtonClicked())); - - mapper_ = new QDataWidgetMapper(this); - hasValidIndex_ = false; - model_ = NULL; -} - -QtHighlightRuleWidget::~QtHighlightRuleWidget() -{ -} - -/** Widget does not gain ownership over the model */ -void QtHighlightRuleWidget::setModel(QtHighlightRulesItemModel* model) -{ - model_ = model; - mapper_->setModel(model_); -} - -void QtHighlightRuleWidget::setActiveIndex(const QModelIndex& index) -{ - if (index.isValid()) { - if (!hasValidIndex_) { - mapper_->addMapping(ui_.applyTo, QtHighlightRulesItemModel::ApplyTo, "currentIndex"); - mapper_->addMapping(ui_.senders, QtHighlightRulesItemModel::Sender, "plainText"); - mapper_->addMapping(ui_.keywords, QtHighlightRulesItemModel::Keyword, "plainText"); - mapper_->addMapping(ui_.nickIsKeyword, QtHighlightRulesItemModel::NickIsKeyword); - mapper_->addMapping(ui_.matchCase, QtHighlightRulesItemModel::MatchCase); - mapper_->addMapping(ui_.matchWholeWords, QtHighlightRulesItemModel::MatchWholeWords); - mapper_->addMapping(ui_.highlightText, QtHighlightRulesItemModel::HighlightText); - mapper_->addMapping(ui_.foreground, QtHighlightRulesItemModel::TextColor, "color"); - mapper_->addMapping(ui_.background, QtHighlightRulesItemModel::TextBackground, "color"); - mapper_->addMapping(ui_.playSound, QtHighlightRulesItemModel::PlaySound); - mapper_->addMapping(ui_.soundFile, QtHighlightRulesItemModel::SoundFile); - } - mapper_->setCurrentModelIndex(index); - ui_.customColors->setChecked(ui_.foreground->getColor().isValid() || ui_.background->getColor().isValid()); - ui_.customSound->setChecked(!ui_.soundFile->text().isEmpty()); - ui_.applyTo->focusWidget(); - } else { - if (hasValidIndex_) { - mapper_->clearMapping(); - } - } - - hasValidIndex_ = index.isValid(); -} - -void QtHighlightRuleWidget::onCustomColorsToggled(bool enabled) -{ - if (!enabled) { - ui_.foreground->setColor(QColor()); - ui_.background->setColor(QColor()); - } - ui_.foreground->setEnabled(enabled); - ui_.background->setEnabled(enabled); -} - -void QtHighlightRuleWidget::onCustomSoundToggled(bool enabled) -{ - if (enabled) { - if (ui_.soundFile->text().isEmpty()) { - onSoundFileButtonClicked(); - } - } else { - ui_.soundFile->clear(); - } - ui_.soundFile->setEnabled(enabled); - ui_.soundFileButton->setEnabled(enabled); -} - -void QtHighlightRuleWidget::onSoundFileButtonClicked() -{ - QString s = QFileDialog::getOpenFileName(this, tr("Choose sound file"), QString(), tr("Sound files (*.wav)")); - if (!s.isEmpty()) { - ui_.soundFile->setText(s); - } -} - -void QtHighlightRuleWidget::onHighlightTextToggled(bool enabled) -{ - ui_.customColors->setEnabled(enabled); -} - -void QtHighlightRuleWidget::onPlaySoundToggled(bool enabled) -{ - ui_.customSound->setEnabled(enabled); -} - -void QtHighlightRuleWidget::save() -{ - if (hasValidIndex_) { - mapper_->submit(); - } -} - -void QtHighlightRuleWidget::revert() -{ - if (hasValidIndex_) { - mapper_->revert(); - } -} - -} diff --git a/Swift/QtUI/QtHighlightRuleWidget.h b/Swift/QtUI/QtHighlightRuleWidget.h deleted file mode 100644 index 8a59a14..0000000 --- a/Swift/QtUI/QtHighlightRuleWidget.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2012 Maciej Niedzielski - * Licensed under the simplified BSD license. - * See Documentation/Licenses/BSD-simplified.txt for more information. - */ - -#pragma once - -#include <QWidget> -#include <QModelIndex> - -#include <Swift/QtUI/ui_QtHighlightRuleWidget.h> - -class QDataWidgetMapper; - -namespace Swift { - - class QtHighlightRulesItemModel; - - class QtHighlightRuleWidget : public QWidget - { - Q_OBJECT - - public: - explicit QtHighlightRuleWidget(QWidget* parent = NULL); - ~QtHighlightRuleWidget(); - - void setModel(QtHighlightRulesItemModel* model); - - public slots: - void setActiveIndex(const QModelIndex&); - void save(); - void revert(); - - private slots: - void onHighlightTextToggled(bool); - void onCustomColorsToggled(bool); - void onPlaySoundToggled(bool); - void onCustomSoundToggled(bool); - void onSoundFileButtonClicked(); - - private: - QDataWidgetMapper * mapper_; - QtHighlightRulesItemModel * model_; - bool hasValidIndex_; - Ui::QtHighlightRuleWidget ui_; - }; - -} diff --git a/Swift/QtUI/QtHighlightRuleWidget.ui b/Swift/QtUI/QtHighlightRuleWidget.ui deleted file mode 100644 index 9c465f9..0000000 --- a/Swift/QtUI/QtHighlightRuleWidget.ui +++ /dev/null @@ -1,260 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>QtHighlightRuleWidget</class> - <widget class="QWidget" name="QtHighlightRuleWidget"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>361</width> - <height>524</height> - </rect> - </property> - <property name="windowTitle"> - <string>Form</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>Rule conditions</string> - </property> - <layout class="QFormLayout" name="formLayout"> - <property name="fieldGrowthPolicy"> - <enum>QFormLayout::ExpandingFieldsGrow</enum> - </property> - <item row="0" column="0" colspan="2"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Choose when this rule should be applied. -If you want to provide more than one sender or keyword, input them in separate lines.</string> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="0" colspan="2"> - <widget class="Line" name="line"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>&Apply to:</string> - </property> - <property name="buddy"> - <cstring>applyTo</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QComboBox" name="applyTo"/> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>&Senders:</string> - </property> - <property name="buddy"> - <cstring>senders</cstring> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QPlainTextEdit" name="senders"/> - </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>&Keywords:</string> - </property> - <property name="buddy"> - <cstring>keywords</cstring> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QPlainTextEdit" name="keywords"/> - </item> - <item row="5" column="1"> - <widget class="QCheckBox" name="nickIsKeyword"> - <property name="text"> - <string>Treat &nick as a keyword (in MUC)</string> - </property> - </widget> - </item> - <item row="6" column="1"> - <widget class="QCheckBox" name="matchWholeWords"> - <property name="text"> - <string>Match whole &words</string> - </property> - </widget> - </item> - <item row="7" column="1"> - <widget class="QCheckBox" name="matchCase"> - <property name="text"> - <string>Match &case</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>Actions</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QCheckBox" name="highlightText"> - <property name="text"> - <string>&Highlight text</string> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>28</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QCheckBox" name="customColors"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Custom c&olors:</string> - </property> - </widget> - </item> - <item> - <widget class="Swift::QtColorToolButton" name="foreground"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>&Foreground</string> - </property> - <property name="toolButtonStyle"> - <enum>Qt::ToolButtonTextBesideIcon</enum> - </property> - </widget> - </item> - <item> - <widget class="Swift::QtColorToolButton" name="background"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>&Background</string> - </property> - <property name="toolButtonStyle"> - <enum>Qt::ToolButtonTextBesideIcon</enum> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QCheckBox" name="playSound"> - <property name="text"> - <string>&Play sound</string> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>28</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QCheckBox" name="customSound"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Custom soun&d:</string> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="soundFile"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="soundFileButton"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>...</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>101</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <customwidgets> - <customwidget> - <class>Swift::QtColorToolButton</class> - <extends>QToolButton</extends> - <header>QtColorToolButton.h</header> - </customwidget> - </customwidgets> - <resources/> - <connections/> -</ui> diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index 701170c..b0c1492 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -1,63 +1,63 @@ /* * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swift/QtUI/QtUIFactory.h> #include <QSplitter> #include <Swift/QtUI/QtXMLConsoleWidget.h> #include <Swift/QtUI/QtChatTabs.h> #include <Swift/QtUI/QtMainWindow.h> #include <Swift/QtUI/QtLoginWindow.h> #include <Swift/QtUI/QtSystemTray.h> #include <Swift/QtUI/QtSettingsProvider.h> #include <Swift/QtUI/QtMainWindow.h> #include <Swift/QtUI/QtChatWindow.h> #include <Swift/QtUI/QtJoinMUCWindow.h> #include <Swift/QtUI/QtChatWindowFactory.h> #include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/MUCSearch/QtMUCSearchWindow.h> #include <Swift/QtUI/UserSearch/QtUserSearchWindow.h> #include <Swift/QtUI/QtProfileWindow.h> #include <Swift/QtUI/QtContactEditWindow.h> #include <Swift/QtUI/QtAdHocCommandWindow.h> #include <Swift/QtUI/QtFileTransferListWidget.h> -#include <Swift/QtUI/QtHighlightEditorWidget.h> +#include <Swift/QtUI/QtHighlightEditor.h> #include <Swift/QtUI/Whiteboard/QtWhiteboardWindow.h> #include <Swift/Controllers/Settings/SettingsProviderHierachy.h> #include <Swift/QtUI/QtUISettingConstants.h> #include <Swift/QtUI/QtHistoryWindow.h> #include <Swiften/Whiteboard/WhiteboardSession.h> #include <Swift/QtUI/QtSingleWindow.h> #include <Swift/QtUI/QtBlockListEditorWindow.h> namespace Swift { QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist, bool enableAdHocCommandOnJID) : settings(settings), qtOnlySettings(qtOnlySettings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), statusCache(statusCache), startMinimized(startMinimized), emoticonsExist_(emoticonsExist), enableAdHocCommandOnJID_(enableAdHocCommandOnJID) { chatFontSize = settings->getSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE); historyFontSize_ = settings->getSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE); } XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() { QtXMLConsoleWidget* widget = new QtXMLConsoleWidget(); tabs->addTab(widget); if (!tabs->isVisible()) { tabs->show(); } widget->show(); return widget; } HistoryWindow* QtUIFactory::createHistoryWindow(UIEventStream* uiEventStream) { QtHistoryWindow* window = new QtHistoryWindow(settings, uiEventStream); tabs->addTab(window); if (!tabs->isVisible()) { tabs->show(); } connect(window, SIGNAL(fontResized(int)), this, SLOT(handleHistoryWindowFontResized(int))); window->handleFontResized(historyFontSize_); @@ -132,48 +132,48 @@ ChatWindow* QtUIFactory::createChatWindow(const JID& contact, UIEventStream* eve } } foreach (QPointer<QtChatWindow> deletedWindow, deletions) { chatWindows.erase(std::remove(chatWindows.begin(), chatWindows.end(), deletedWindow), chatWindows.end()); } connect(window, SIGNAL(fontResized(int)), this, SLOT(handleChatWindowFontResized(int))); window->handleFontResized(chatFontSize); return window; } void QtUIFactory::handleChatWindowFontResized(int size) { chatFontSize = size; settings->storeSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE, size); } UserSearchWindow* QtUIFactory::createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups) { return new QtUserSearchWindow(eventStream, type, groups, qtOnlySettings); } JoinMUCWindow* QtUIFactory::createJoinMUCWindow(UIEventStream* uiEventStream) { return new QtJoinMUCWindow(uiEventStream); } ProfileWindow* QtUIFactory::createProfileWindow() { return new QtProfileWindow(); } ContactEditWindow* QtUIFactory::createContactEditWindow() { return new QtContactEditWindow(); } WhiteboardWindow* QtUIFactory::createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession) { return new QtWhiteboardWindow(whiteboardSession); } -HighlightEditorWidget* QtUIFactory::createHighlightEditorWidget() { - return new QtHighlightEditorWidget(); +HighlightEditorWindow* QtUIFactory::createHighlightEditorWindow() { + return new QtHighlightEditor(qtOnlySettings); } BlockListEditorWidget *QtUIFactory::createBlockListEditorWidget() { return new QtBlockListEditorWindow(); } AdHocCommandWindow* QtUIFactory::createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) { return new QtAdHocCommandWindow(command); } } diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index 4c50572..9c07e76 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -16,63 +16,63 @@ class QSplitter; namespace Swift { class QtSettingsProvider; class SettingsProviderHierachy; class QtChatTabs; class QtSystemTray; class QtLoginWindow; class QtMainWindow; class QtChatTheme; class QtChatWindowFactory; class QtChatWindow; class TimerFactory; class historyWindow_; class WhiteboardSession; class StatusCache; class QtSingleWindow; class QtUIFactory : public QObject, public UIFactory { Q_OBJECT public: QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist, bool enableAdHocCommandOnJID); virtual XMLConsoleWidget* createXMLConsoleWidget(); virtual HistoryWindow* createHistoryWindow(UIEventStream*); virtual MainWindow* createMainWindow(UIEventStream* eventStream); virtual LoginWindow* createLoginWindow(UIEventStream* eventStream); virtual EventWindow* createEventWindow(); virtual ChatListWindow* createChatListWindow(UIEventStream*); virtual MUCSearchWindow* createMUCSearchWindow(); virtual ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream); virtual UserSearchWindow* createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups); virtual JoinMUCWindow* createJoinMUCWindow(UIEventStream* uiEventStream); virtual ProfileWindow* createProfileWindow(); virtual ContactEditWindow* createContactEditWindow(); virtual FileTransferListWidget* createFileTransferListWidget(); virtual WhiteboardWindow* createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession); - virtual HighlightEditorWidget* createHighlightEditorWidget(); + virtual HighlightEditorWindow* createHighlightEditorWindow(); virtual BlockListEditorWidget* createBlockListEditorWidget(); virtual AdHocCommandWindow* createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command); private slots: void handleLoginWindowGeometryChanged(); void handleChatWindowFontResized(int); void handleHistoryWindowFontResized(int); private: SettingsProviderHierachy* settings; QtSettingsProvider* qtOnlySettings; QtChatTabs* tabs; QtSingleWindow* netbookSplitter; QtSystemTray* systemTray; QtChatWindowFactory* chatWindowFactory; TimerFactory* timerFactory_; QtMainWindow* lastMainWindow; QtLoginWindow* loginWindow; StatusCache* statusCache; std::vector<QPointer<QtChatWindow> > chatWindows; bool startMinimized; int chatFontSize; int historyFontSize_; bool emoticonsExist_; bool enableAdHocCommandOnJID_; }; } diff --git a/Swift/QtUI/QtWebKitChatView.cpp b/Swift/QtUI/QtWebKitChatView.cpp index 23bc099..1486293 100644 --- a/Swift/QtUI/QtWebKitChatView.cpp +++ b/Swift/QtUI/QtWebKitChatView.cpp @@ -504,118 +504,121 @@ void QtWebKitChatView::handleScrollRequested(int, int dy, const QRect&) { else if (pos == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)) { emit scrollReachedBottom(); } } int QtWebKitChatView::getSnippetPositionByDate(const QDate& date) { QWebElement message = webPage_->mainFrame()->documentElement().findFirst(".date" + date.toString(Qt::ISODate)); return message.geometry().top(); } void QtWebKitChatView::resetTopInsertPoint() { QWebElement continuationElement = firstElement_.findFirst("#insert"); continuationElement.removeFromDocument(); firstElement_ = QWebElement(); topInsertPoint_.removeFromDocument(); QWebElement chatElement = document_.findFirst("#Chat"); topInsertPoint_ = chatElement.clone(); topInsertPoint_.setOuterXml("<div id='swift_insert'/>"); chatElement.prependInside(topInsertPoint_); } std::string QtWebKitChatView::addMessage( const ChatWindow::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, "", time, highlight, ChatSnippet::getDirection(message)); } +QString QtWebKitChatView::getHighlightSpanStart(const std::string& text, const std::string& background) { + QString ecsapeColor = QtUtilities::htmlEscape(P2QSTRING(text)); + QString escapeBackground = QtUtilities::htmlEscape(P2QSTRING(background)); + if (ecsapeColor.isEmpty()) { + ecsapeColor = "black"; + } + if (escapeBackground.isEmpty()) { + escapeBackground = "yellow"; + } + return QString("<span style=\"color: %1; background: %2\">").arg(ecsapeColor).arg(escapeBackground); +} + +QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) { + return getHighlightSpanStart(highlight.getTextColor(), highlight.getTextBackground()); +} + QString QtWebKitChatView::chatMessageToHTML(const ChatWindow::ChatMessage& message) { QString result; foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, message.getParts()) { boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; boost::shared_ptr<ChatWindow::ChatURIMessagePart> uriPart; boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart; boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart; if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { QString text = QtUtilities::htmlEscape(P2QSTRING(textPart->text)); text.replace("\n","<br/>"); result += text; continue; } if ((uriPart = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(part))) { QString uri = QtUtilities::htmlEscape(P2QSTRING(uriPart->target)); result += "<a href='" + uri + "' >" + uri + "</a>"; continue; } if ((emoticonPart = boost::dynamic_pointer_cast<ChatWindow::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<ChatWindow::ChatHighlightingMessagePart>(part))) { - //FIXME: Maybe do something here. Anything, really. + QString spanStart = getHighlightSpanStart(highlightPart->foregroundColor, highlightPart->backgroundColor); + result += spanStart + QtUtilities::htmlEscape(P2QSTRING(highlightPart->text)) + "</span>"; continue; } } return result; } - -QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) { - QString color = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextColor())); - QString background = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextBackground())); - if (color.isEmpty()) { - color = "black"; - } - if (background.isEmpty()) { - background = "yellow"; - } - - return QString("<span style=\"color: %1; background: %2\">").arg(color).arg(background); -} - std::string QtWebKitChatView::addMessage( const QString& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const QString& style, const boost::posix_time::ptime& time, const HighlightAction& highlight, ChatSnippet::Direction direction) { QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str()); QString htmlString; if (label) { 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 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 + message + highlightSpanEnd + styleSpanEnd + "</span>" ; bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf); QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded(); std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++); addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), direction)); previousMessageWasSelf_ = senderIsSelf; previousSenderName_ = P2QSTRING(senderName); previousMessageKind_ = PreviousMessageWasMessage; return id; diff --git a/Swift/QtUI/QtWebKitChatView.h b/Swift/QtUI/QtWebKitChatView.h index fb6e4da..925ceeb 100644 --- a/Swift/QtUI/QtWebKitChatView.h +++ b/Swift/QtUI/QtWebKitChatView.h @@ -1,37 +1,37 @@ /* - * Copyright (c) 2010-2013 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once #include <QString> #include <QWidget> #include <QList> #include <QWebElement> #include <boost/shared_ptr.hpp> #include <Swiften/Base/Override.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> #include <Swift/QtUI/ChatSnippet.h> #include <Swift/QtUI/QtChatView.h> class QWebPage; class QUrl; class QDate; namespace Swift { class QtWebView; class QtChatTheme; class QtChatWindowJSBridge; class UIEventStream; class QtChatWindow; class QtWebKitChatView : public QtChatView { Q_OBJECT public: static const QString ButtonWhiteboardSessionCancel; @@ -116,72 +116,73 @@ namespace Swift { private slots: void handleViewLoadFinished(bool); void handleFrameSizeChanged(); void handleClearRequested(); void handleScrollRequested(int dx, int dy, const QRect& rectToScroll); void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3, QString arg4, QString arg5); private: enum PreviousMessageKind { PreviosuMessageWasNone, PreviousMessageWasMessage, PreviousMessageWasSystem, PreviousMessageWasPresence, PreviousMessageWasFileTransfer, PreviousMessageWasMUCInvite }; std::string addMessage( const QString& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const QString& style, const boost::posix_time::ptime& time, const HighlightAction& highlight, ChatSnippet::Direction direction); void replaceMessage( const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style, const HighlightAction& highlight); bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf); static ChatSnippet::Direction getActualDirection(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction); - QString chatMessageToHTML(const ChatWindow::ChatMessage& message); + QString getHighlightSpanStart(const std::string& text, const std::string& background); QString getHighlightSpanStart(const HighlightAction& highlight); + QString chatMessageToHTML(const ChatWindow::ChatMessage& message); static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString(), const QString& arg4 = QString(), const QString& arg5 = QString()); private: void headerEncode(); void messageEncode(); void addToDOM(boost::shared_ptr<ChatSnippet> snippet); QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet); QtChatWindow* window_; UIEventStream* eventStream_; bool viewReady_; bool isAtBottom_; bool topMessageAdded_; int scrollBarMaximum_; QtWebView* webView_; QWebPage* webPage_; int fontSizeSteps_; QtChatTheme* theme_; QWebElement newInsertPoint_; QWebElement topInsertPoint_; QWebElement lineSeparator_; QWebElement lastElement_; QWebElement firstElement_; QWebElement document_; bool disableAutoScroll_; QtChatWindowJSBridge* jsBridge; PreviousMessageKind previousMessageKind_; bool previousMessageWasSelf_; bool showEmoticons_; bool insertingLastLine_; int idCounter_; QString previousSenderName_; std::map<QString, QString> descriptions_; }; } diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index dd7d0c3..26e738a 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -95,73 +95,71 @@ sources = [ "QtLoginWindow.cpp", "QtMainWindow.cpp", "QtProfileWindow.cpp", "QtBlockListEditorWindow.cpp", "QtNameWidget.cpp", "QtSettingsProvider.cpp", "QtStatusWidget.cpp", "QtScaledAvatarCache.cpp", "QtSwift.cpp", "QtURIHandler.cpp", "QtChatWindow.cpp", "QtChatView.cpp", "QtWebKitChatView.cpp", "QtPlainChatView.cpp", "QtChatTheme.cpp", "QtChatTabs.cpp", "QtSoundPlayer.cpp", "QtSystemTray.cpp", "QtCachedImageScaler.cpp", "QtTabbable.cpp", "QtTabWidget.cpp", "QtTextEdit.cpp", "QtXMLConsoleWidget.cpp", "QtHistoryWindow.cpp", "QtFileTransferListWidget.cpp", "QtFileTransferListItemModel.cpp", "QtAdHocCommandWindow.cpp", "QtAdHocCommandWithJIDWindow.cpp", "QtUtilities.cpp", "QtBookmarkDetailWindow.cpp", "QtAddBookmarkWindow.cpp", "QtEditBookmarkWindow.cpp", "QtContactEditWindow.cpp", "QtContactEditWidget.cpp", "QtSingleWindow.cpp", - "QtHighlightEditorWidget.cpp", - "QtHighlightRulesItemModel.cpp", - "QtHighlightRuleWidget.cpp", + "QtHighlightEditor.cpp", "QtColorToolButton.cpp", "QtClosableLineEdit.cpp", "ChatSnippet.cpp", "MessageSnippet.cpp", "SystemMessageSnippet.cpp", "QtElidingLabel.cpp", "QtFormWidget.cpp", "QtFormResultItemModel.cpp", "QtLineEdit.cpp", "QtJoinMUCWindow.cpp", "QtConnectionSettingsWindow.cpp", "Roster/RosterModel.cpp", "Roster/QtTreeWidget.cpp", # "Roster/QtTreeWidgetItem.cpp", "Roster/RosterDelegate.cpp", "Roster/GroupItemDelegate.cpp", "Roster/DelegateCommons.cpp", "Roster/QtFilterWidget.cpp", "Roster/QtRosterWidget.cpp", "Roster/QtOccupantListWidget.cpp", "Roster/RosterTooltip.cpp", "EventViewer/EventModel.cpp", "EventViewer/EventDelegate.cpp", "EventViewer/TwoLineDelegate.cpp", "EventViewer/QtEventWindow.cpp", "EventViewer/QtEvent.cpp", "ChatList/QtChatListWindow.cpp", "ChatList/ChatListModel.cpp", "ChatList/ChatListDelegate.cpp", "ChatList/ChatListMUCItem.cpp", "ChatList/ChatListRecentItem.cpp", "ChatList/ChatListWhiteboardItem.cpp", "MUCSearch/QtMUCSearchWindow.cpp", "MUCSearch/MUCSearchModel.cpp", "MUCSearch/MUCSearchRoomItem.cpp", @@ -254,72 +252,71 @@ if env["PLATFORM"] == "win32" : ] if env["PLATFORM"] == "posix" : sources += [ "FreeDesktopNotifier.cpp", "QtDBUSURIHandler.cpp", ] if env["PLATFORM"] == "darwin" : sources += ["CocoaApplicationActivateHelper.mm"] sources += ["CocoaUIHelpers.mm"] if env["PLATFORM"] == "darwin" or env["PLATFORM"] == "win32" : swiftProgram = myenv.Program("Swift", sources) else : sources += ["QtCertificateViewerDialog.cpp"]; myenv.Uic4("QtCertificateViewerDialog.ui"); swiftProgram = myenv.Program("swift-im", sources) if env["PLATFORM"] != "darwin" and env["PLATFORM"] != "win32" : openURIProgram = myenv.Program("swift-open-uri", "swift-open-uri.cpp") else : openURIProgram = [] myenv.Uic4("MUCSearch/QtMUCSearchWindow.ui") myenv.Uic4("UserSearch/QtUserSearchWizard.ui") myenv.Uic4("UserSearch/QtUserSearchFirstPage.ui") myenv.Uic4("UserSearch/QtUserSearchFirstMultiJIDPage.ui") myenv.Uic4("UserSearch/QtUserSearchFieldsPage.ui") myenv.Uic4("UserSearch/QtUserSearchResultsPage.ui") myenv.Uic4("QtBookmarkDetailWindow.ui") myenv.Uic4("QtAffiliationEditor.ui") myenv.Uic4("QtJoinMUCWindow.ui") myenv.Uic4("QtHistoryWindow.ui") myenv.Uic4("QtConnectionSettings.ui") -myenv.Uic4("QtHighlightRuleWidget.ui") -myenv.Uic4("QtHighlightEditorWidget.ui") +myenv.Uic4("QtHighlightEditor.ui") myenv.Uic4("QtBlockListEditorWindow.ui") myenv.Uic4("QtSpellCheckerWindow.ui") myenv.Qrc("DefaultTheme.qrc") myenv.Qrc("Swift.qrc") # Resources commonResources = { "": ["#/Swift/resources/sounds"] } myenv["TEXTFILESUFFIX"] = "" myenv.MyTextfile(target = "COPYING", source = [myenv.File("../../COPYING.gpl"), myenv.File("../../COPYING.thirdparty")], LINESEPARATOR = "\n\n========\n\n\n") ################################################################################ # Translation ################################################################################ # Collect available languages translation_languages = [] for file in os.listdir(Dir("#/Swift/Translations").abspath) : if file.startswith("swift_") and file.endswith(".ts") : translation_languages.append(file[6:-3]) # Generate translation modules translation_sources = [env.File("#/Swift/Translations/swift.ts").abspath] translation_modules = [] for lang in translation_languages : translation_resource = "#/Swift/resources/translations/swift_" + lang + ".qm" translation_source = "#/Swift/Translations/swift_" + lang + ".ts" translation_sources.append(env.File(translation_source).abspath) translation_modules.append(env.File(translation_resource).abspath) myenv.Qm(translation_resource, translation_source) commonResources["translations"] = commonResources.get("translations", []) + [translation_resource] # LUpdate translation (if requested) |
Swift