summaryrefslogtreecommitdiffstats
path: root/Swift
diff options
context:
space:
mode:
authorMaciej Niedzielski <machekku@uaznia.net>2012-12-21 19:58:24 (GMT)
committerMaciej Niedzielski <machekku@uaznia.net>2013-01-09 12:34:06 (GMT)
commit4ed137080a3d80d20a2cead47f741e3dd2f2d42e (patch)
treef030f0d9b8e61733de4e2bec9cef7715d380af8f /Swift
parenta8e2d82a1be5e94ac39523fc3e0606fcc261e913 (diff)
downloadswift-4ed137080a3d80d20a2cead47f741e3dd2f2d42e.zip
swift-4ed137080a3d80d20a2cead47f741e3dd2f2d42e.tar.bz2
Highlighting support
Change-Id: Ib6bd42cecff018998117bc1e7db279a62b3af434 License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.
Diffstat (limited to 'Swift')
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp15
-rw-r--r--Swift/Controllers/Chat/ChatController.h5
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.cpp29
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.h12
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp10
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h4
-rw-r--r--Swift/Controllers/Chat/MUCController.cpp27
-rw-r--r--Swift/Controllers/Chat/MUCController.h6
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp5
-rw-r--r--Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp9
-rw-r--r--Swift/Controllers/HighlightAction.cpp28
-rw-r--r--Swift/Controllers/HighlightAction.h45
-rw-r--r--Swift/Controllers/HighlightEditorController.cpp40
-rw-r--r--Swift/Controllers/HighlightEditorController.h38
-rw-r--r--Swift/Controllers/HighlightManager.cpp139
-rw-r--r--Swift/Controllers/HighlightManager.h49
-rw-r--r--Swift/Controllers/HighlightRule.cpp200
-rw-r--r--Swift/Controllers/HighlightRule.h77
-rw-r--r--Swift/Controllers/Highlighter.cpp41
-rw-r--r--Swift/Controllers/Highlighter.h37
-rw-r--r--Swift/Controllers/MainController.cpp14
-rw-r--r--Swift/Controllers/MainController.h4
-rw-r--r--Swift/Controllers/SConscript8
-rw-r--r--Swift/Controllers/SettingConstants.cpp1
-rw-r--r--Swift/Controllers/SettingConstants.h1
-rw-r--r--Swift/Controllers/SoundEventController.cpp19
-rw-r--r--Swift/Controllers/SoundEventController.h6
-rw-r--r--Swift/Controllers/SoundPlayer.h4
-rw-r--r--Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h16
-rw-r--r--Swift/Controllers/UIInterfaces/ChatWindow.h9
-rw-r--r--Swift/Controllers/UIInterfaces/HighlightEditorWidget.h22
-rw-r--r--Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h20
-rw-r--r--Swift/Controllers/UIInterfaces/UIFactory.h4
-rw-r--r--Swift/Controllers/UnitTest/HighlightRuleTest.cpp318
-rw-r--r--Swift/Controllers/UnitTest/MockChatWindow.h8
-rw-r--r--Swift/QtUI/QtChatWindow.cpp42
-rw-r--r--Swift/QtUI/QtChatWindow.h13
-rw-r--r--Swift/QtUI/QtColorToolButton.cpp45
-rw-r--r--Swift/QtUI/QtColorToolButton.h32
-rw-r--r--Swift/QtUI/QtHighlightEditorWidget.cpp149
-rw-r--r--Swift/QtUI/QtHighlightEditorWidget.h44
-rw-r--r--Swift/QtUI/QtHighlightEditorWidget.ui124
-rw-r--r--Swift/QtUI/QtHighlightRuleWidget.cpp134
-rw-r--r--Swift/QtUI/QtHighlightRuleWidget.h49
-rw-r--r--Swift/QtUI/QtHighlightRuleWidget.ui260
-rw-r--r--Swift/QtUI/QtHighlightRulesItemModel.cpp284
-rw-r--r--Swift/QtUI/QtHighlightRulesItemModel.h64
-rw-r--r--Swift/QtUI/QtLoginWindow.cpp9
-rw-r--r--Swift/QtUI/QtLoginWindow.h2
-rw-r--r--Swift/QtUI/QtSoundPlayer.cpp8
-rw-r--r--Swift/QtUI/QtSoundPlayer.h2
-rw-r--r--Swift/QtUI/QtUIFactory.cpp5
-rw-r--r--Swift/QtUI/QtUIFactory.h1
-rw-r--r--Swift/QtUI/SConscript6
54 files changed, 2465 insertions, 78 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 16b22fe..0ffef0c 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -32,7 +32,7 @@
#include <Swiften/Elements/DeliveryReceipt.h>
#include <Swiften/Elements/DeliveryReceiptRequest.h>
#include <Swift/Controllers/SettingConstants.h>
-
+#include <Swift/Controllers/Highlighter.h>
#include <Swiften/Base/Log.h>
namespace Swift {
@@ -40,8 +40,8 @@ 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)
- : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings) {
+ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager)
+ : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings) {
isInMUC_ = isInMUC;
lastWasPresence_ = false;
chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider);
@@ -174,8 +174,11 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me
}
}
-void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
+void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) {
eventController_->handleIncomingEvent(messageEvent);
+ if (!messageEvent->getConcluded()) {
+ highlighter_->handleHighlightAction(highlight);
+ }
}
@@ -211,9 +214,9 @@ void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<
boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>();
if (replace) {
eraseIf(unackedStanzas_, PairSecondEquals<boost::shared_ptr<Stanza>, std::string>(myLastMessageUIID_));
- replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time());
+ replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time(), HighlightAction());
} else {
- myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time());
+ myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time(), HighlightAction());
}
if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) {
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 66ec37d..6021ec1 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -22,10 +22,11 @@ namespace Swift {
class FileTransferController;
class SettingsProvider;
class HistoryController;
+ class HighlightManager;
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);
+ 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);
virtual ~ChatController();
virtual void setToJID(const JID& jid);
virtual void setOnline(bool online);
@@ -45,7 +46,7 @@ namespace Swift {
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);
+ 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;
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index d380cd5..ad7f76a 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -31,15 +31,18 @@
#include <Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h>
+#include <Swift/Controllers/HighlightManager.h>
+#include <Swift/Controllers/Highlighter.h>
namespace Swift {
-ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry) {
+ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager) : 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) {
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();
}
@@ -176,19 +179,19 @@ 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 std::string& avatarPath, const boost::posix_time::ptime& time) {
+std::string ChatControllerBase::addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
if (boost::starts_with(message, "/me ")) {
- return chatWindow_->addAction(String::getSplittedAtFirst(message, ' ').second, senderName, senderIsSelf, label, avatarPath, time);
+ return chatWindow_->addAction(String::getSplittedAtFirst(message, ' ').second, senderName, senderIsSelf, label, avatarPath, time, highlight);
} else {
- return chatWindow_->addMessage(message, senderName, senderIsSelf, label, avatarPath, time);
+ return chatWindow_->addMessage(message, senderName, senderIsSelf, label, avatarPath, time, highlight);
}
}
-void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) {
+void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
if (boost::starts_with(message, "/me ")) {
- chatWindow_->replaceWithAction(String::getSplittedAtFirst(message, ' ').second, id, time);
+ chatWindow_->replaceWithAction(String::getSplittedAtFirst(message, ' ').second, id, time, highlight);
} else {
- chatWindow_->replaceMessage(message, id, time);
+ chatWindow_->replaceMessage(message, id, time, highlight);
}
}
@@ -206,6 +209,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
}
boost::shared_ptr<Message> message = messageEvent->getStanza();
std::string body = message->getBody();
+ HighlightAction highlight;
if (message->isError()) {
std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>()));
chatWindow_->addErrorMessage(errorMessage);
@@ -244,6 +248,11 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
}
onActivity(body);
+ // Highlight
+ if (!isIncomingMessageFromMe(message)) {
+ highlight = highlighter_->findAction(body, senderDisplayNameFromMessage(from));
+ }
+
boost::shared_ptr<Replace> replace = message->getPayload<Replace>();
if (replace) {
std::string body = message->getBody();
@@ -251,11 +260,11 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
std::map<JID, std::string>::iterator lastMessage;
lastMessage = lastMessagesUIID_.find(from);
if (lastMessage != lastMessagesUIID_.end()) {
- replaceMessage(body, lastMessagesUIID_[from], timeStamp);
+ replaceMessage(body, lastMessagesUIID_[from], timeStamp, highlight);
}
}
else {
- lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp);
+ lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp, highlight);
}
logMessage(body, from, selfJID_, timeStamp, true);
@@ -263,7 +272,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
chatWindow_->show();
chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size()));
onUnreadCountChanged();
- postHandleIncomingMessage(messageEvent);
+ postHandleIncomingMessage(messageEvent, highlight);
}
std::string ChatControllerBase::getErrorMessage(boost::shared_ptr<ErrorPayload> error) {
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index 02cf9f6..baef9e6 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -29,6 +29,7 @@
#include "Swiften/Base/IDGenerator.h"
#include <Swift/Controllers/HistoryController.h>
#include <Swiften/MUC/MUCRegistry.h>
+#include <Swift/Controllers/HighlightManager.h>
namespace Swift {
class IQRouter;
@@ -39,6 +40,8 @@ namespace Swift {
class UIEventStream;
class EventController;
class EntityCapsProvider;
+ class HighlightManager;
+ class Highlighter;
class ChatControllerBase : public boost::bsignals::trackable {
public:
@@ -47,8 +50,8 @@ namespace Swift {
void activateChatWindow();
void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
void handleIncomingMessage(boost::shared_ptr<MessageEvent> message);
- std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time);
- void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time);
+ std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& 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);
virtual void setOnline(bool online);
virtual void setEnabled(bool enabled);
virtual void setToJID(const JID& jid) {toJID_ = jid;}
@@ -60,7 +63,7 @@ namespace Swift {
void handleCapsChanged(const JID& jid);
protected:
- ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry);
+ 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);
/**
* Pass the Message appended, and the stanza used to send it.
@@ -69,7 +72,7 @@ namespace Swift {
virtual std::string senderDisplayNameFromMessage(const JID& from) = 0;
virtual bool isIncomingMessageFromMe(boost::shared_ptr<Message>) = 0;
virtual void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {}
- virtual void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {}
+ virtual void 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;
@@ -116,5 +119,6 @@ namespace Swift {
SecurityLabelsCatalog::Item lastLabel_;
HistoryController* historyController_;
MUCRegistry* mucRegistry_;
+ Highlighter* highlighter_;
};
}
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 1e0e9c2..dba8565 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -74,7 +74,8 @@ ChatsManager::ChatsManager(
bool eagleMode,
SettingsProvider* settings,
HistoryController* historyController,
- WhiteboardManager* whiteboardManager) :
+ WhiteboardManager* whiteboardManager,
+ HighlightManager* highlightManager) :
jid_(jid),
joinMUCWindowFactory_(joinMUCWindowFactory),
useDelayForLatency_(useDelayForLatency),
@@ -86,7 +87,8 @@ ChatsManager::ChatsManager(
eagleMode_(eagleMode),
settings_(settings),
historyController_(historyController),
- whiteboardManager_(whiteboardManager) {
+ whiteboardManager_(whiteboardManager),
+ highlightManager_(highlightManager) {
timerFactory_ = timerFactory;
eventController_ = eventController;
stanzaChannel_ = stanzaChannel;
@@ -521,7 +523,7 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact)
ChatController* ChatsManager::createNewChatController(const JID& contact) {
assert(chatControllers_.find(contact) == chatControllers_.end());
- ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_);
+ 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_);
chatControllers_[contact] = controller;
controller->setAvailableServerFeatures(serverDiscoInfo_);
controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
@@ -594,7 +596,7 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional
if (createAsReservedIfNew) {
muc->setCreateAsReservedIfNew();
}
- MUCController* controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_);
+ MUCController* controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_);
mucControllers_[mucJID] = controller;
controller->setAvailableServerFeatures(serverDiscoInfo_);
controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller));
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 5b8b785..55e62b9 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -50,10 +50,11 @@ namespace Swift {
class SettingsProvider;
class WhiteboardManager;
class HistoryController;
+ class HighlightManager;
class ChatsManager {
public:
- ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager);
+ ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager);
virtual ~ChatsManager();
void setAvatarManager(AvatarManager* avatarManager);
void setOnline(bool enabled);
@@ -136,5 +137,6 @@ namespace Swift {
SettingsProvider* settings_;
HistoryController* historyController_;
WhiteboardManager* whiteboardManager_;
+ HighlightManager* highlightManager_;
};
}
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index d966d3f..937116f 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -35,6 +35,7 @@
#include <Swift/Controllers/Roster/SetPresence.h>
#include <Swiften/Disco/EntityCapsProvider.h>
#include <Swiften/Roster/XMPPRoster.h>
+#include <Swift/Controllers/Highlighter.h>
#define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000
@@ -61,8 +62,9 @@ MUCController::MUCController (
EntityCapsProvider* entityCapsProvider,
XMPPRoster* roster,
HistoryController* historyController,
- MUCRegistry* mucRegistry) :
- ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry), muc_(muc), nick_(nick), desiredNick_(nick), password_(password) {
+ MUCRegistry* mucRegistry,
+ HighlightManager* highlightManager) :
+ ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager), muc_(muc), nick_(nick), desiredNick_(nick), password_(password) {
parting_ = true;
joined_ = false;
lastWasPresence_ = false;
@@ -98,6 +100,8 @@ MUCController::MUCController (
muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1));
muc_->onRoleChangeFailed.connect(boost::bind(&MUCController::handleOccupantRoleChangeFailed, this, _1, _2, _3));
muc_->onAffiliationListReceived.connect(boost::bind(&MUCController::handleAffiliationListReceived, this, _1, _2));
+ highlighter_->setMode(Highlighter::MUCMode);
+ highlighter_->setNick(nick_);
if (timerFactory) {
loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS));
loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this));
@@ -273,7 +277,7 @@ void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) {
chatWindow_->addErrorMessage(errorMessage);
parting_ = true;
if (!rejoinNick.empty()) {
- nick_ = rejoinNick;
+ setNick(rejoinNick);
rejoin();
}
}
@@ -284,7 +288,7 @@ void MUCController::handleJoinComplete(const std::string& nick) {
receivedActivity();
joined_ = true;
std::string joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick);
- nick_ = nick;
+ setNick(nick);
chatWindow_->addSystemMessage(joinMessage);
#ifdef SWIFT_EXPERIMENTAL_HISTORY
@@ -455,10 +459,13 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes
}
}
-void MUCController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
+void MUCController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) {
boost::shared_ptr<Message> message = messageEvent->getStanza();
if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>()) {
eventController_->handleIncomingEvent(messageEvent);
+ if (!messageEvent->getConcluded()) {
+ highlighter_->handleHighlightAction(highlight);
+ }
}
}
@@ -510,7 +517,7 @@ void MUCController::setOnline(bool online) {
if (loginCheckTimer_) {
loginCheckTimer_->start();
}
- nick_ = desiredNick_;
+ setNick(desiredNick_);
rejoin();
}
}
@@ -818,7 +825,7 @@ void MUCController::addRecentLogs() {
bool senderIsSelf = nick_ == message.getFromJID().getResource();
// the chatWindow uses utc timestamps
- addMessage(message.getMessage(), senderDisplayNameFromMessage(message.getFromJID()), senderIsSelf, boost::shared_ptr<SecurityLabel>(new SecurityLabel()), std::string(avatarManager_->getAvatarPath(message.getFromJID()).string()), message.getTime() - boost::posix_time::hours(message.getOffset()));
+ addMessage(message.getMessage(), senderDisplayNameFromMessage(message.getFromJID()), senderIsSelf, boost::shared_ptr<SecurityLabel>(new SecurityLabel()), std::string(avatarManager_->getAvatarPath(message.getFromJID()).string()), message.getTime() - boost::posix_time::hours(message.getOffset()), HighlightAction());
}
}
@@ -847,4 +854,10 @@ void MUCController::checkDuplicates(boost::shared_ptr<Message> newMessage) {
}
}
+void MUCController::setNick(const std::string& nick)
+{
+ nick_ = nick;
+ highlighter_->setNick(nick_);
+}
+
}
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index 7e81f3d..11fe0ff 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -33,6 +33,7 @@ namespace Swift {
class TabComplete;
class InviteToChatWindow;
class XMPPRoster;
+ class HighlightManager;
enum JoinPart {Join, Part, JoinThenPart, PartThenJoin};
@@ -44,7 +45,7 @@ namespace Swift {
class MUCController : public ChatControllerBase {
public:
- MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry);
+ MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager);
~MUCController();
boost::signal<void ()> onUserLeft;
boost::signal<void ()> onUserJoined;
@@ -62,7 +63,7 @@ namespace Swift {
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>);
+ 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);
@@ -108,6 +109,7 @@ namespace Swift {
void handleInviteToMUCWindowCompleted();
void addRecentLogs();
void checkDuplicates(boost::shared_ptr<Message> newMessage);
+ void setNick(const std::string& nick);
private:
MUC::ref muc_;
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index aab582c..dd90d66 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -106,14 +106,16 @@ public:
avatarManager_ = new NullAvatarManager();
wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsManager_);
wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_);
+ highlightManager_ = new HighlightManager(settings_);
mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_);
- manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_);
+ manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_);
manager_->setAvatarManager(avatarManager_);
}
void tearDown() {
+ delete highlightManager_;
//delete chatListWindowFactory
delete profileSettings_;
delete avatarManager_;
@@ -481,6 +483,7 @@ private:
FileTransferManager* ftManager_;
WhiteboardSessionManager* wbSessionManager_;
WhiteboardManager* wbManager_;
+ HighlightManager* highlightManager_;
};
CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest);
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index ab83bc2..f1fcf79 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -26,6 +26,7 @@
#include "Swiften/Network/TimerFactory.h"
#include "Swiften/Elements/MUCUserPayload.h"
#include "Swiften/Disco/DummyEntityCapsProvider.h"
+#include <Swift/Controllers/Settings/DummySettingsProvider.h>
using namespace Swift;
@@ -62,12 +63,16 @@ public:
window_ = new MockChatWindow();
mucRegistry_ = new MUCRegistry();
entityCapsProvider_ = new DummyEntityCapsProvider();
+ settings_ = new DummySettingsProvider();
+ highlightManager_ = new HighlightManager(settings_);
muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_);
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
- controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_);
+ controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_);
}
void tearDown() {
+ delete highlightManager_;
+ delete settings_;
delete entityCapsProvider_;
delete controller_;
delete eventController_;
@@ -338,6 +343,8 @@ private:
MockChatWindow* window_;
MUCRegistry* mucRegistry_;
DummyEntityCapsProvider* entityCapsProvider_;
+ DummySettingsProvider* settings_;
+ HighlightManager* highlightManager_;
};
CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest);
diff --git a/Swift/Controllers/HighlightAction.cpp b/Swift/Controllers/HighlightAction.cpp
new file mode 100644
index 0000000..d4d199d
--- /dev/null
+++ b/Swift/Controllers/HighlightAction.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/Controllers/HighlightAction.h>
+
+namespace Swift {
+
+void HighlightAction::setHighlightText(bool highlightText)
+{
+ highlightText_ = highlightText;
+ if (!highlightText_) {
+ textColor_.clear();
+ textBackground_.clear();
+ }
+}
+
+void HighlightAction::setPlaySound(bool playSound)
+{
+ playSound_ = playSound;
+ if (!playSound_) {
+ soundFile_.clear();
+ }
+}
+
+}
diff --git a/Swift/Controllers/HighlightAction.h b/Swift/Controllers/HighlightAction.h
new file mode 100644
index 0000000..bfbed74
--- /dev/null
+++ b/Swift/Controllers/HighlightAction.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace Swift {
+
+ class HighlightRule;
+
+ class HighlightAction {
+ public:
+ HighlightAction() : highlightText_(false), playSound_(false) {}
+
+ bool highlightText() const { return highlightText_; }
+ void setHighlightText(bool highlightText);
+
+ const std::string& getTextColor() const { return textColor_; }
+ void setTextColor(const std::string& textColor) { textColor_ = textColor; }
+
+ const std::string& getTextBackground() const { return textBackground_; }
+ void setTextBackground(const std::string& textBackground) { textBackground_ = textBackground; }
+
+ bool playSound() const { return playSound_; }
+ void setPlaySound(bool playSound);
+
+ const std::string& getSoundFile() const { return soundFile_; }
+ void setSoundFile(const std::string& soundFile) { soundFile_ = soundFile; }
+
+ bool isEmpty() const { return !highlightText_ && !playSound_; }
+
+ private:
+ bool highlightText_;
+ std::string textColor_;
+ std::string textBackground_;
+
+ bool playSound_;
+ std::string soundFile_;
+ };
+
+}
diff --git a/Swift/Controllers/HighlightEditorController.cpp b/Swift/Controllers/HighlightEditorController.cpp
new file mode 100644
index 0000000..899e4bb
--- /dev/null
+++ b/Swift/Controllers/HighlightEditorController.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.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>
+
+namespace Swift {
+
+HighlightEditorController::HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWidgetFactory* highlightEditorWidgetFactory, HighlightManager* highlightManager) : highlightEditorWidgetFactory_(highlightEditorWidgetFactory), highlightEditorWidget_(NULL), highlightManager_(highlightManager)
+{
+ uiEventStream->onUIEvent.connect(boost::bind(&HighlightEditorController::handleUIEvent, this, _1));
+}
+
+HighlightEditorController::~HighlightEditorController()
+{
+ delete highlightEditorWidget_;
+ highlightEditorWidget_ = 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_);
+ }
+ highlightEditorWidget_->show();
+ }
+}
+
+}
diff --git a/Swift/Controllers/HighlightEditorController.h b/Swift/Controllers/HighlightEditorController.h
new file mode 100644
index 0000000..3868251
--- /dev/null
+++ b/Swift/Controllers/HighlightEditorController.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+namespace Swift {
+
+ class UIEventStream;
+
+ class HighlightEditorWidgetFactory;
+ class HighlightEditorWidget;
+
+ class HighlightManager;
+
+ class HighlightEditorController {
+ public:
+ HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWidgetFactory* highlightEditorWidgetFactory, HighlightManager* highlightManager);
+ ~HighlightEditorController();
+
+ HighlightManager* getHighlightManager() const { return highlightManager_; }
+
+ private:
+ void handleUIEvent(boost::shared_ptr<UIEvent> event);
+
+ private:
+ HighlightEditorWidgetFactory* highlightEditorWidgetFactory_;
+ HighlightEditorWidget* highlightEditorWidget_;
+ HighlightManager* highlightManager_;
+ };
+
+}
diff --git a/Swift/Controllers/HighlightManager.cpp b/Swift/Controllers/HighlightManager.cpp
new file mode 100644
index 0000000..74a07c0
--- /dev/null
+++ b/Swift/Controllers/HighlightManager.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <cassert>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/regex.hpp>
+#include <boost/bind.hpp>
+#include <boost/numeric/conversion/cast.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)
+{
+ 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::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 && boost::numeric_cast<std::vector<std::string>::size_type>(index) < rules_.size());
+ return rules_[index];
+}
+
+void HighlightManager::setRule(int index, const HighlightRule& rule)
+{
+ assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) < rules_.size());
+ rules_[index] = rule;
+ storeSettings();
+}
+
+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();
+}
+
+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();
+}
+
+Highlighter* HighlightManager::createHighlighter()
+{
+ return new Highlighter(this);
+}
+
+}
diff --git a/Swift/Controllers/HighlightManager.h b/Swift/Controllers/HighlightManager.h
new file mode 100644
index 0000000..d195d05
--- /dev/null
+++ b/Swift/Controllers/HighlightManager.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.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:
+ HighlightManager(SettingsProvider* settings);
+
+ Highlighter* createHighlighter();
+
+ const std::vector<HighlightRule>& 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);
+
+ 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_;
+ };
+
+}
diff --git a/Swift/Controllers/HighlightRule.cpp b/Swift/Controllers/HighlightRule.cpp
new file mode 100644
index 0000000..01d1228
--- /dev/null
+++ b/Swift/Controllers/HighlightRule.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <algorithm>
+#include <boost/algorithm/string.hpp>
+#include <boost/lambda/lambda.hpp>
+
+#include <Swiften/Base/foreach.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
+{
+ // escape regex special characters: ^.$| etc
+ // these need to be escaped: [\^\$\|.........]
+ // and then C++ requires '\' to be escaped, too....
+ static const boost::regex esc("([\\^\\.\\$\\|\\(\\)\\[\\]\\*\\+\\?\\/\\{\\}\\\\])");
+ // matched character should be prepended with '\'
+ // replace matched special character with \\\1
+ // and escape once more for C++ rules...
+ static const std::string rep("\\\\\\1");
+ std::string escaped = boost::regex_replace(s , esc, rep);
+
+ std::string 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;
+ int 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)
+{
+ nickIsKeyword_ = nickIsKeyword;
+ updateRegex();
+}
+
+void HighlightRule::setMatchCase(bool matchCase)
+{
+ matchCase_ = matchCase;
+ updateRegex();
+}
+
+void HighlightRule::setMatchWholeWords(bool matchWholeWords)
+{
+ matchWholeWords_ = matchWholeWords;
+ updateRegex();
+}
+
+void HighlightRule::setMatchChat(bool matchChat)
+{
+ matchChat_ = matchChat;
+ updateRegex();
+}
+
+void HighlightRule::setMatchMUC(bool matchMUC)
+{
+ matchMUC_ = matchMUC;
+ updateRegex();
+}
+
+bool HighlightRule::isEmpty() const
+{
+ return senders_.empty() && keywords_.empty() && !nickIsKeyword_ && !matchChat_ && !matchMUC_ && action_.isEmpty();
+}
+
+}
diff --git a/Swift/Controllers/HighlightRule.h b/Swift/Controllers/HighlightRule.h
new file mode 100644
index 0000000..1abfa5a
--- /dev/null
+++ b/Swift/Controllers/HighlightRule.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+#include <string>
+
+#include <boost/regex.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<std::string>& getKeywords() const { return keywords_; }
+ void setKeywords(const std::vector<std::string>&);
+
+ 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:
+ 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_;
+ };
+
+}
diff --git a/Swift/Controllers/Highlighter.cpp b/Swift/Controllers/Highlighter.cpp
new file mode 100644
index 0000000..754641a
--- /dev/null
+++ b/Swift/Controllers/Highlighter.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.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();
+ }
+ }
+
+ return HighlightAction();
+}
+
+void Highlighter::handleHighlightAction(const HighlightAction& action)
+{
+ manager_->onHighlight(action);
+}
+
+}
diff --git a/Swift/Controllers/Highlighter.h b/Swift/Controllers/Highlighter.h
new file mode 100644
index 0000000..d026f29
--- /dev/null
+++ b/Swift/Controllers/Highlighter.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <Swift/Controllers/HighlightRule.h>
+
+namespace Swift {
+
+ class HighlightManager;
+
+ class Highlighter {
+ public:
+ Highlighter(HighlightManager* manager);
+
+ enum Mode { ChatMode, MUCMode };
+ void setMode(Mode mode);
+
+ void setNick(const std::string& nick) { nick_ = nick; }
+
+ HighlightAction findAction(const std::string& body, const std::string& sender) const;
+
+ void handleHighlightAction(const HighlightAction& action);
+
+ private:
+ HighlightManager* manager_;
+ Mode mode_;
+ HighlightRule::MessageType messageType_;
+ std::string nick_;
+ };
+
+}
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index 195eeaf..bb74ed7 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -84,6 +84,8 @@
#include <Swiften/Client/ClientXMLTracer.h>
#include <Swift/Controllers/SettingConstants.h>
#include <Swiften/Client/StanzaChannel.h>
+#include <Swift/Controllers/HighlightManager.h>
+#include <Swift/Controllers/HighlightEditorController.h>
namespace Swift {
@@ -148,7 +150,11 @@ MainController::MainController(
systemTrayController_ = new SystemTrayController(eventController_, systemTray);
loginWindow_ = uiFactory_->createLoginWindow(uiEventStream_);
loginWindow_->setShowNotificationToggle(!notifier->isExternallyConfigured());
- soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings);
+
+ highlightManager_ = new HighlightManager(settings_);
+ highlightEditorController_ = new HighlightEditorController(uiEventStream_, uiFactory_, highlightManager_);
+
+ soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings, highlightManager_);
xmppURIController_ = new XMPPURIController(uriHandler_, uiEventStream_);
@@ -208,6 +214,8 @@ MainController::~MainController() {
eventController_->disconnectAll();
resetClient();
+ delete highlightEditorController_;
+ delete highlightManager_;
delete fileTransferListController_;
delete xmlConsoleController_;
delete xmppURIController_;
@@ -324,9 +332,9 @@ void MainController::handleConnected() {
#ifdef SWIFT_EXPERIMENTAL_HISTORY
historyController_ = new HistoryController(storages_->getHistoryStorage());
historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_);
- chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_);
+ 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_);
#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_);
+ 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_);
#endif
client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1));
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index 2e5bd05..fc8d518 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -71,6 +71,8 @@ namespace Swift {
class AdHocCommandWindowFactory;
class FileTransferOverview;
class WhiteboardManager;
+ class HighlightManager;
+ class HighlightEditorController;
class MainController {
public:
@@ -176,5 +178,7 @@ namespace Swift {
static const int SecondsToWaitBeforeForceQuitting;
FileTransferOverview* ftOverview_;
WhiteboardManager* whiteboardManager_;
+ HighlightManager* highlightManager_;
+ HighlightEditorController* highlightEditorController_;
};
}
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index a54c6a2..cd88dd9 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -75,7 +75,12 @@ if env["SCONS_STAGE"] == "build" :
"ChatMessageSummarizer.cpp",
"SettingConstants.cpp",
"WhiteboardManager.cpp",
- "StatusCache.cpp"
+ "StatusCache.cpp",
+ "HighlightAction.cpp",
+ "HighlightEditorController.cpp",
+ "HighlightManager.cpp",
+ "HighlightRule.cpp",
+ "Highlighter.cpp"
])
env.Append(UNITTEST_SOURCES = [
@@ -90,4 +95,5 @@ if env["SCONS_STAGE"] == "build" :
File("UnitTest/MockChatWindow.cpp"),
File("UnitTest/ChatMessageSummarizerTest.cpp"),
File("Settings/UnitTest/SettingsProviderHierachyTest.cpp"),
+ File("UnitTest/HighlightRuleTest.cpp"),
])
diff --git a/Swift/Controllers/SettingConstants.cpp b/Swift/Controllers/SettingConstants.cpp
index 7ab4ac4..e430c77 100644
--- a/Swift/Controllers/SettingConstants.cpp
+++ b/Swift/Controllers/SettingConstants.cpp
@@ -19,4 +19,5 @@ const SettingsProvider::Setting<bool> SettingConstants::LOGIN_AUTOMATICALLY = Se
const SettingsProvider::Setting<bool> SettingConstants::SHOW_OFFLINE("showOffline", false);
const SettingsProvider::Setting<std::string> SettingConstants::EXPANDED_ROSTER_GROUPS("GroupExpandiness", "");
const SettingsProvider::Setting<bool> SettingConstants::PLAY_SOUNDS("playSounds", true);
+const SettingsProvider::Setting<std::string> SettingConstants::HIGHLIGHT_RULES("highlightRules", "@");
}
diff --git a/Swift/Controllers/SettingConstants.h b/Swift/Controllers/SettingConstants.h
index ff1ed72..cc3af47 100644
--- a/Swift/Controllers/SettingConstants.h
+++ b/Swift/Controllers/SettingConstants.h
@@ -22,5 +22,6 @@ namespace Swift {
static const SettingsProvider::Setting<bool> SHOW_OFFLINE;
static const SettingsProvider::Setting<std::string> EXPANDED_ROSTER_GROUPS;
static const SettingsProvider::Setting<bool> PLAY_SOUNDS;
+ static const SettingsProvider::Setting<std::string> HIGHLIGHT_RULES;
};
}
diff --git a/Swift/Controllers/SoundEventController.cpp b/Swift/Controllers/SoundEventController.cpp
index d056990..a5171e2 100644
--- a/Swift/Controllers/SoundEventController.cpp
+++ b/Swift/Controllers/SoundEventController.cpp
@@ -12,22 +12,33 @@
#include <Swift/Controllers/SoundPlayer.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/SettingConstants.h>
+#include <Swift/Controllers/HighlightManager.h>
namespace Swift {
-SoundEventController::SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings) {
+SoundEventController::SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings, HighlightManager* highlightManager) {
settings_ = settings;
eventController_ = eventController;
soundPlayer_ = soundPlayer;
eventController_->onEventQueueEventAdded.connect(boost::bind(&SoundEventController::handleEventQueueEventAdded, this, _1));
+ highlightManager_ = highlightManager;
+ highlightManager_->onHighlight.connect(boost::bind(&SoundEventController::handleHighlight, this, _1));
+
settings_->onSettingChanged.connect(boost::bind(&SoundEventController::handleSettingChanged, this, _1));
playSounds_ = settings->getSetting(SettingConstants::PLAY_SOUNDS);
}
-void SoundEventController::handleEventQueueEventAdded(boost::shared_ptr<StanzaEvent> event) {
- if (playSounds_ && !event->getConcluded()) {
- soundPlayer_->playSound(SoundPlayer::MessageReceived);
+void SoundEventController::handleEventQueueEventAdded(boost::shared_ptr<StanzaEvent> /*event*/) {
+ // message received sound is now played via highlighting
+ //if (playSounds_ && !event->getConcluded()) {
+ // soundPlayer_->playSound(SoundPlayer::MessageReceived);
+ //}
+}
+
+void SoundEventController::handleHighlight(const HighlightAction& action) {
+ if (playSounds_ && action.playSound()) {
+ soundPlayer_->playSound(SoundPlayer::MessageReceived, action.getSoundFile());
}
}
diff --git a/Swift/Controllers/SoundEventController.h b/Swift/Controllers/SoundEventController.h
index 842125d..c9dcab4 100644
--- a/Swift/Controllers/SoundEventController.h
+++ b/Swift/Controllers/SoundEventController.h
@@ -10,21 +10,25 @@
#include <Swift/Controllers/XMPPEvents/StanzaEvent.h>
#include <Swift/Controllers/Settings/SettingsProvider.h>
+#include <Swift/Controllers/HighlightAction.h>
namespace Swift {
class EventController;
class SoundPlayer;
+ class HighlightManager;
class SoundEventController {
public:
- SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings);
+ SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings, HighlightManager* highlightManager);
void setPlaySounds(bool playSounds);
bool getSoundEnabled() {return playSounds_;}
private:
void handleSettingChanged(const std::string& settingPath);
void handleEventQueueEventAdded(boost::shared_ptr<StanzaEvent> event);
+ void handleHighlight(const HighlightAction& action);
EventController* eventController_;
SoundPlayer* soundPlayer_;
bool playSounds_;
SettingsProvider* settings_;
+ HighlightManager* highlightManager_;
};
}
diff --git a/Swift/Controllers/SoundPlayer.h b/Swift/Controllers/SoundPlayer.h
index 19bf8b6..f18a2c0 100644
--- a/Swift/Controllers/SoundPlayer.h
+++ b/Swift/Controllers/SoundPlayer.h
@@ -6,11 +6,13 @@
#pragma once
+#include <string>
+
namespace Swift {
class SoundPlayer {
public:
virtual ~SoundPlayer() {}
enum SoundEffect{MessageReceived};
- virtual void playSound(SoundEffect sound) = 0;
+ virtual void playSound(SoundEffect sound, const std::string& soundResource) = 0;
};
}
diff --git a/Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h b/Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h
new file mode 100644
index 0000000..42e22a2
--- /dev/null
+++ b/Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+namespace Swift {
+
+ class RequestHighlightEditorUIEvent : public UIEvent {
+ };
+
+}
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index ad0ed15..252e43d 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -17,6 +17,7 @@
#include <Swiften/Elements/ChatState.h>
#include <Swiften/Elements/Form.h>
#include <Swiften/Elements/MUCOccupant.h>
+#include <Swift/Controllers/HighlightManager.h>
namespace Swift {
@@ -44,16 +45,16 @@ namespace Swift {
/** Add message to window.
* @return id of added message (for acks).
*/
- virtual std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) = 0;
+ virtual std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
/** Adds action to window.
* @return id of added message (for acks);
*/
- virtual std::string addAction(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) = 0;
+ virtual std::string addAction(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
virtual void addSystemMessage(const std::string& message) = 0;
virtual void addPresenceMessage(const std::string& message) = 0;
virtual void addErrorMessage(const std::string& message) = 0;
- virtual void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) = 0;
- virtual void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) = 0;
+ virtual void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
+ virtual void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
// File transfer related stuff
virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) = 0;
diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWidget.h b/Swift/Controllers/UIInterfaces/HighlightEditorWidget.h
new file mode 100644
index 0000000..4745035
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/HighlightEditorWidget.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+namespace Swift {
+
+ class HighlightManager;
+
+ class HighlightEditorWidget {
+ public:
+ virtual ~HighlightEditorWidget() {}
+
+ virtual void show() = 0;
+
+ virtual void setHighlightManager(HighlightManager* highlightManager) = 0;
+ };
+
+}
diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h b/Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h
new file mode 100644
index 0000000..ade575b
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+namespace Swift {
+
+ class HighlightEditorWidget;
+
+ class HighlightEditorWidgetFactory {
+ public:
+ virtual ~HighlightEditorWidgetFactory() {}
+
+ virtual HighlightEditorWidget* createHighlightEditorWidget() = 0;
+ };
+
+}
diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h
index 6b4efd8..dcd1779 100644
--- a/Swift/Controllers/UIInterfaces/UIFactory.h
+++ b/Swift/Controllers/UIInterfaces/UIFactory.h
@@ -21,6 +21,7 @@
#include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h>
#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h>
namespace Swift {
class UIFactory :
@@ -38,7 +39,8 @@ namespace Swift {
public ContactEditWindowFactory,
public AdHocCommandWindowFactory,
public FileTransferListWidgetFactory,
- public WhiteboardWindowFactory {
+ public WhiteboardWindowFactory,
+ public HighlightEditorWidgetFactory {
public:
virtual ~UIFactory() {}
};
diff --git a/Swift/Controllers/UnitTest/HighlightRuleTest.cpp b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp
new file mode 100644
index 0000000..ec81227
--- /dev/null
+++ b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <vector>
+#include <string>
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <Swift/Controllers/HighlightRule.h>
+
+using namespace Swift;
+
+class HighlightRuleTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(HighlightRuleTest);
+ CPPUNIT_TEST(testEmptyRuleNeverMatches);
+ CPPUNIT_TEST(testKeyword);
+ CPPUNIT_TEST(testNickKeyword);
+ CPPUNIT_TEST(testNickWithoutOtherKeywords);
+ CPPUNIT_TEST(testSender);
+ CPPUNIT_TEST(testSenderAndKeyword);
+ CPPUNIT_TEST(testWholeWords);
+ CPPUNIT_TEST(testCase);
+ CPPUNIT_TEST(testWholeWordsAndCase);
+ CPPUNIT_TEST(testMUC);
+ CPPUNIT_TEST_SUITE_END();
+
+ public:
+ void setUp() {
+ std::vector<std::string> keywords;
+ keywords.push_back("keyword1");
+ keywords.push_back("KEYWORD2");
+
+ std::vector<std::string>senders;
+ senders.push_back("sender1");
+ senders.push_back("SENDER2");
+
+ emptyRule = new HighlightRule();
+
+ keywordRule = new HighlightRule();
+ keywordRule->setKeywords(keywords);
+
+ keywordChatRule = new HighlightRule();
+ keywordChatRule->setKeywords(keywords);
+ keywordChatRule->setMatchChat(true);
+
+ keywordNickChatRule = new HighlightRule();
+ keywordNickChatRule->setKeywords(keywords);
+ keywordNickChatRule->setNickIsKeyword(true);
+ keywordNickChatRule->setMatchChat(true);
+
+ nickChatRule = new HighlightRule();
+ nickChatRule->setNickIsKeyword(true);
+ nickChatRule->setMatchChat(true);
+
+ nickRule = new HighlightRule();
+ nickRule->setNickIsKeyword(true);
+
+ senderRule = new HighlightRule();
+ senderRule->setSenders(senders);
+
+ senderChatRule = new HighlightRule();
+ senderChatRule->setSenders(senders);
+ senderChatRule->setMatchChat(true);
+
+ senderKeywordChatRule = new HighlightRule();
+ senderKeywordChatRule->setSenders(senders);
+ senderKeywordChatRule->setKeywords(keywords);
+ senderKeywordChatRule->setMatchChat(true);
+
+ senderKeywordNickChatRule = new HighlightRule();
+ senderKeywordNickChatRule->setSenders(senders);
+ senderKeywordNickChatRule->setKeywords(keywords);
+ senderKeywordNickChatRule->setNickIsKeyword(true);
+ senderKeywordNickChatRule->setMatchChat(true);
+
+ senderKeywordNickWordChatRule = new HighlightRule();
+ senderKeywordNickWordChatRule->setSenders(senders);
+ senderKeywordNickWordChatRule->setKeywords(keywords);
+ senderKeywordNickWordChatRule->setNickIsKeyword(true);
+ senderKeywordNickWordChatRule->setMatchWholeWords(true);
+ senderKeywordNickWordChatRule->setMatchChat(true);
+
+ senderKeywordNickCaseChatRule = new HighlightRule();
+ senderKeywordNickCaseChatRule->setSenders(senders);
+ senderKeywordNickCaseChatRule->setKeywords(keywords);
+ senderKeywordNickCaseChatRule->setNickIsKeyword(true);
+ senderKeywordNickCaseChatRule->setMatchCase(true);
+ senderKeywordNickCaseChatRule->setMatchChat(true);
+
+ senderKeywordNickCaseWordChatRule = new HighlightRule();
+ senderKeywordNickCaseWordChatRule->setSenders(senders);
+ senderKeywordNickCaseWordChatRule->setKeywords(keywords);
+ senderKeywordNickCaseWordChatRule->setNickIsKeyword(true);
+ senderKeywordNickCaseWordChatRule->setMatchCase(true);
+ senderKeywordNickCaseWordChatRule->setMatchWholeWords(true);
+ senderKeywordNickCaseWordChatRule->setMatchChat(true);
+
+ senderKeywordNickMUCRule = new HighlightRule();
+ senderKeywordNickMUCRule->setSenders(senders);
+ senderKeywordNickMUCRule->setKeywords(keywords);
+ senderKeywordNickMUCRule->setNickIsKeyword(true);
+ senderKeywordNickMUCRule->setMatchMUC(true);
+ }
+
+ void tearDown() {
+ delete emptyRule;
+
+ delete keywordRule;
+ delete keywordChatRule;
+ delete keywordNickChatRule;
+ delete nickChatRule;
+ delete nickRule;
+
+ delete senderRule;
+ delete senderChatRule;
+ delete senderKeywordChatRule;
+ delete senderKeywordNickChatRule;
+
+ delete senderKeywordNickWordChatRule;
+ delete senderKeywordNickCaseChatRule;
+ delete senderKeywordNickCaseWordChatRule;
+
+ delete senderKeywordNickMUCRule;
+ }
+
+ void testEmptyRuleNeverMatches() {
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false);
+
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "", HighlightRule::MUCMessage), false);
+
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "nick", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "nick", HighlightRule::MUCMessage), false);
+
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "nick", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "nick", HighlightRule::MUCMessage), false);
+
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "", HighlightRule::MUCMessage), false);
+
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "", HighlightRule::MUCMessage), false);
+
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "nick", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "nick", HighlightRule::MUCMessage), false);
+
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "", HighlightRule::MUCMessage), false);
+ }
+
+ void testKeyword() {
+ CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false);
+ CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false);
+
+ CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), true);
+ 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("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("keyword2", "from", "nick", HighlightRule::ChatMessage), true);
+ }
+
+ 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("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);
+ }
+
+ 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("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("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("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("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("body contains nick", "sender1", "nick", HighlightRule::MUCMessage), true);
+ }
+
+ private:
+ HighlightRule* emptyRule;
+
+ HighlightRule* keywordRule;
+ HighlightRule* keywordChatRule;
+ HighlightRule* keywordNickChatRule;
+ HighlightRule* nickChatRule;
+ HighlightRule* nickRule;
+
+ HighlightRule* senderRule;
+ HighlightRule* senderChatRule;
+ HighlightRule* senderKeywordChatRule;
+ HighlightRule* senderKeywordNickChatRule;
+
+ HighlightRule* senderKeywordNickWordChatRule;
+ HighlightRule* senderKeywordNickCaseChatRule;
+ HighlightRule* senderKeywordNickCaseWordChatRule;
+
+ HighlightRule* senderKeywordNickMUCRule;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(HighlightRuleTest);
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index ac3f21b..84aaa04 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -14,8 +14,8 @@ namespace Swift {
MockChatWindow() : labelsEnabled_(false) {}
virtual ~MockChatWindow();
- virtual std::string addMessage(const std::string& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime&) {lastMessageBody_ = message; return "";}
- virtual std::string addAction(const std::string& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime&) {lastMessageBody_ = message; return "";}
+ virtual std::string addMessage(const std::string& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime&, const HighlightAction&) {lastMessageBody_ = message; return "";}
+ virtual std::string addAction(const std::string& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime&, const HighlightAction&) {lastMessageBody_ = message; return "";}
virtual void addSystemMessage(const std::string& /*message*/) {}
virtual void addErrorMessage(const std::string& /*message*/) {}
virtual void addPresenceMessage(const std::string& /*message*/) {}
@@ -41,8 +41,8 @@ namespace Swift {
virtual void setRosterModel(Roster* /*roster*/) {}
virtual void setTabComplete(TabComplete*) {}
virtual void replaceLastMessage(const std::string&) {}
- virtual void replaceMessage(const std::string&, const std::string&, const boost::posix_time::ptime&) {}
- virtual void replaceWithAction(const std::string& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/) {}
+ virtual void replaceMessage(const std::string&, const std::string&, const boost::posix_time::ptime&, const HighlightAction&) {}
+ virtual void replaceWithAction(const std::string& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction&) {}
void setAckState(const std::string& /*id*/, AckState /*state*/) {}
virtual void flash() {}
virtual void setAlert(const std::string& /*alertText*/, const std::string& /*buttonText*/) {}
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index 5d57184..a53ca5d 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -481,8 +481,8 @@ void QtChatWindow::updateTitleWithUnreadCount() {
emit titleUpdated();
}
-std::string QtChatWindow::addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) {
- return addMessage(linkimoticonify(P2QSTRING(message)), senderName, senderIsSelf, label, avatarPath, "", time);
+std::string QtChatWindow::addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
+ return addMessage(linkimoticonify(P2QSTRING(message)), senderName, senderIsSelf, label, avatarPath, "", time, highlight);
}
QString QtChatWindow::linkimoticonify(const QString& message) const {
@@ -502,7 +502,21 @@ QString QtChatWindow::linkimoticonify(const QString& message) const {
return messageHTML;
}
-std::string QtChatWindow::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) {
+QString QtChatWindow::getHighlightSpanStart(const HighlightAction& highlight)
+{
+ QString color = Qt::escape(P2QSTRING(highlight.getTextColor()));
+ QString background = Qt::escape(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 QtChatWindow::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) {
if (isWidgetSelected()) {
onAllMessagesRead();
}
@@ -516,7 +530,9 @@ std::string QtChatWindow::addMessage(const QString &message, const std::string &
QString messageHTML(message);
QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">";
QString styleSpanEnd = style == "" ? "" : "</span>";
- htmlString += "<span class='swift_inner_message'>" + styleSpanStart + messageHTML + styleSpanEnd + "</span>" ;
+ QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : "";
+ QString highlightSpanEnd = highlight.highlightText() ? "</span>" : "";
+ htmlString += "<span class='swift_inner_message'>" + styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd + "</span>" ;
bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf);
if (lastLineTracker_.getShouldMoveLastLine()) {
@@ -572,8 +588,8 @@ int QtChatWindow::getCount() {
return unreadCount_;
}
-std::string QtChatWindow::addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) {
- return addMessage(" *" + linkimoticonify(P2QSTRING(message)) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time);
+std::string QtChatWindow::addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
+ return addMessage(" *" + linkimoticonify(P2QSTRING(message)) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time, highlight);
}
// FIXME: Move this to a different file
@@ -770,15 +786,15 @@ void QtChatWindow::addSystemMessage(const std::string& message) {
previousMessageKind_ = PreviousMessageWasSystem;
}
-void QtChatWindow::replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) {
- replaceMessage(" *" + linkimoticonify(P2QSTRING(message)) + "*", id, time, "font-style:italic ");
+void QtChatWindow::replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
+ replaceMessage(" *" + linkimoticonify(P2QSTRING(message)) + "*", id, time, "font-style:italic ", highlight);
}
-void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) {
- replaceMessage(linkimoticonify(P2QSTRING(message)), id, time, "");
+void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
+ replaceMessage(linkimoticonify(P2QSTRING(message)), id, time, "", highlight);
}
-void QtChatWindow::replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style) {
+void QtChatWindow::replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style, const HighlightAction& highlight) {
if (!id.empty()) {
if (isWidgetSelected()) {
onAllMessagesRead();
@@ -788,7 +804,9 @@ void QtChatWindow::replaceMessage(const QString& message, const std::string& id,
QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">";
QString styleSpanEnd = style == "" ? "" : "</span>";
- messageHTML = styleSpanStart + messageHTML + styleSpanEnd;
+ QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : "";
+ QString highlightSpanEnd = highlight.highlightText() ? "</span>" : "";
+ messageHTML = styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd;
messageLog_->replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time));
}
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index c32ae83..4abd456 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -88,13 +88,13 @@ namespace Swift {
public:
QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons);
~QtChatWindow();
- std::string addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time);
- std::string addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time);
+ std::string addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
+ std::string addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
void addSystemMessage(const std::string& message);
void addPresenceMessage(const std::string& message);
void addErrorMessage(const std::string& errorMessage);
- void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time);
- void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time);
+ void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight);
+ void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight);
// File transfer related stuff
std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes);
void setFileTransferProgress(std::string id, const int percentageDone);
@@ -192,11 +192,12 @@ namespace Swift {
void beginCorrection();
void cancelCorrection();
void handleSettingChanged(const std::string& setting);
- 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);
- void replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style);
+ 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);
+ void replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style, const HighlightAction& highlight);
void handleOccupantSelectionChanged(RosterItem* item);
bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) const;
QString linkimoticonify(const QString& message) const;
+ QString getHighlightSpanStart(const HighlightAction& highlight);
int unreadCount_;
bool contactIsTyping_;
diff --git a/Swift/QtUI/QtColorToolButton.cpp b/Swift/QtUI/QtColorToolButton.cpp
new file mode 100644
index 0000000..1d379a3
--- /dev/null
+++ b/Swift/QtUI/QtColorToolButton.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <QColorDialog>
+#include <QPainter>
+
+#include <Swift/QtUI/QtColorToolButton.h>
+
+namespace Swift {
+
+QtColorToolButton::QtColorToolButton(QWidget* parent) :
+ QToolButton(parent)
+{
+ connect(this, SIGNAL(clicked()), SLOT(onClicked()));
+ setColorIcon(Qt::transparent);
+}
+
+void QtColorToolButton::setColor(const QColor& color)
+{
+ if (color.isValid() != color_.isValid() || (color.isValid() && color != color_)) {
+ color_ = color;
+ setColorIcon(color_);
+ emit colorChanged(color_);
+ }
+}
+
+void QtColorToolButton::onClicked()
+{
+ QColor c = QColorDialog::getColor(color_, this);
+ if (c.isValid()) {
+ setColor(c);
+ }
+}
+
+void QtColorToolButton::setColorIcon(const QColor& color)
+{
+ QPixmap pix(iconSize());
+ pix.fill(color.isValid() ? color : Qt::transparent);
+ setIcon(pix);
+}
+
+}
diff --git a/Swift/QtUI/QtColorToolButton.h b/Swift/QtUI/QtColorToolButton.h
new file mode 100644
index 0000000..33d195d
--- /dev/null
+++ b/Swift/QtUI/QtColorToolButton.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QToolButton>
+
+namespace Swift {
+
+ class QtColorToolButton : public QToolButton {
+ Q_OBJECT
+ Q_PROPERTY(QColor color READ getColor WRITE setColor NOTIFY colorChanged)
+ public:
+ explicit QtColorToolButton(QWidget* parent = NULL);
+ void setColor(const QColor& color);
+ const QColor& getColor() const { return color_; }
+
+ signals:
+ void colorChanged(const QColor&);
+
+ private slots:
+ void onClicked();
+
+ private:
+ void setColorIcon(const QColor& color);
+ QColor color_;
+ };
+
+}
diff --git a/Swift/QtUI/QtHighlightEditorWidget.cpp b/Swift/QtUI/QtHighlightEditorWidget.cpp
new file mode 100644
index 0000000..7ff094e
--- /dev/null
+++ b/Swift/QtUI/QtHighlightEditorWidget.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <cassert>
+
+#include <Swift/QtUI/QtHighlightEditorWidget.h>
+#include <Swift/QtUI/QtHighlightRulesItemModel.h>
+
+namespace Swift {
+
+QtHighlightEditorWidget::QtHighlightEditorWidget(QWidget* parent)
+ : QWidget(parent)
+{
+ ui_.setupUi(this);
+
+ itemModel_ = new QtHighlightRulesItemModel(this);
+ ui_.treeView->setModel(itemModel_);
+ ui_.ruleWidget->setModel(itemModel_);
+
+ for (int i = 0; i < QtHighlightRulesItemModel::NumberOfColumns; ++i) {
+ switch (i) {
+ case QtHighlightRulesItemModel::ApplyTo:
+ case QtHighlightRulesItemModel::Sender:
+ case QtHighlightRulesItemModel::Keyword:
+ case QtHighlightRulesItemModel::Action:
+ ui_.treeView->showColumn(i);
+ break;
+ default:
+ ui_.treeView->hideColumn(i);
+ break;
+ }
+ }
+
+ setHighlightManager(NULL); // setup buttons for empty rules list
+
+ connect(ui_.treeView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), SLOT(onCurrentRowChanged(QModelIndex)));
+
+ connect(ui_.newButton, SIGNAL(clicked()), SLOT(onNewButtonClicked()));
+ connect(ui_.deleteButton, SIGNAL(clicked()), SLOT(onDeleteButtonClicked()));
+
+ connect(ui_.moveUpButton, SIGNAL(clicked()), SLOT(onMoveUpButtonClicked()));
+ connect(ui_.moveDownButton, SIGNAL(clicked()), SLOT(onMoveDownButtonClicked()));
+
+ connect(ui_.closeButton, SIGNAL(clicked()), SLOT(close()));
+
+ setWindowTitle(tr("Highlight Rules"));
+}
+
+QtHighlightEditorWidget::~QtHighlightEditorWidget()
+{
+}
+
+void QtHighlightEditorWidget::show()
+{
+ if (itemModel_->rowCount(QModelIndex())) {
+ selectRow(0);
+ }
+ QWidget::show();
+ QWidget::activateWindow();
+}
+
+void QtHighlightEditorWidget::setHighlightManager(HighlightManager* highlightManager)
+{
+ itemModel_->setHighlightManager(highlightManager);
+ ui_.newButton->setEnabled(highlightManager != NULL);
+
+ ui_.ruleWidget->setEnabled(false);
+ ui_.deleteButton->setEnabled(false);
+ ui_.moveUpButton->setEnabled(false);
+ ui_.moveDownButton->setEnabled(false);
+}
+
+void QtHighlightEditorWidget::closeEvent(QCloseEvent* event) {
+ ui_.ruleWidget->save();
+ event->accept();
+}
+
+void QtHighlightEditorWidget::onNewButtonClicked()
+{
+ int row = getSelectedRow() + 1;
+ itemModel_->insertRow(row, QModelIndex());
+ selectRow(row);
+}
+
+void QtHighlightEditorWidget::onDeleteButtonClicked()
+{
+ int row = getSelectedRow();
+ assert(row >= 0);
+
+ itemModel_->removeRow(row, QModelIndex());
+ if (row == itemModel_->rowCount(QModelIndex())) {
+ --row;
+ }
+ selectRow(row);
+}
+
+void QtHighlightEditorWidget::onMoveUpButtonClicked()
+{
+ int row = getSelectedRow();
+ assert(row > 0);
+
+ ui_.ruleWidget->save();
+ ui_.ruleWidget->setActiveIndex(QModelIndex());
+ itemModel_->swapRows(row, row - 1);
+ selectRow(row - 1);
+}
+
+void QtHighlightEditorWidget::onMoveDownButtonClicked()
+{
+ int row = getSelectedRow();
+ assert(row < itemModel_->rowCount(QModelIndex()) - 1);
+
+ ui_.ruleWidget->save();
+ ui_.ruleWidget->setActiveIndex(QModelIndex());
+ if (itemModel_->swapRows(row, row + 1)) {
+ selectRow(row + 1);
+ }
+}
+
+void QtHighlightEditorWidget::onCurrentRowChanged(const QModelIndex& index)
+{
+ ui_.ruleWidget->save();
+ ui_.ruleWidget->setActiveIndex(index);
+
+ ui_.ruleWidget->setEnabled(index.isValid());
+
+ ui_.deleteButton->setEnabled(index.isValid());
+
+ ui_.moveUpButton->setEnabled(index.isValid() && index.row() != 0);
+ ui_.moveDownButton->setEnabled(index.isValid() && index.row() != itemModel_->rowCount(QModelIndex()) - 1);
+}
+
+void QtHighlightEditorWidget::selectRow(int row)
+{
+ QModelIndex index = itemModel_->index(row, 0, QModelIndex());
+ ui_.treeView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
+}
+
+/** Return index of selected row or -1 if none is selected */
+int QtHighlightEditorWidget::getSelectedRow() const
+{
+ QModelIndexList rows = ui_.treeView->selectionModel()->selectedRows();
+ return rows.isEmpty() ? -1 : rows[0].row();
+}
+
+}
diff --git a/Swift/QtUI/QtHighlightEditorWidget.h b/Swift/QtUI/QtHighlightEditorWidget.h
new file mode 100644
index 0000000..1293c87
--- /dev/null
+++ b/Swift/QtUI/QtHighlightEditorWidget.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIInterfaces/HighlightEditorWidget.h>
+#include <Swift/QtUI/ui_QtHighlightEditorWidget.h>
+
+namespace Swift {
+
+ class QtHighlightRulesItemModel;
+
+ class QtHighlightEditorWidget : public QWidget, public HighlightEditorWidget {
+ Q_OBJECT
+
+ public:
+ QtHighlightEditorWidget(QWidget* parent = NULL);
+ virtual ~QtHighlightEditorWidget();
+
+ void show();
+
+ void setHighlightManager(HighlightManager* highlightManager);
+
+ private slots:
+ void onNewButtonClicked();
+ void onDeleteButtonClicked();
+ void onMoveUpButtonClicked();
+ void onMoveDownButtonClicked();
+ void onCurrentRowChanged(const QModelIndex&);
+
+ private:
+ virtual void closeEvent(QCloseEvent* event);
+
+ void selectRow(int row);
+ int getSelectedRow() const;
+
+ Ui::QtHighlightEditorWidget ui_;
+ QtHighlightRulesItemModel* itemModel_;
+ };
+
+}
diff --git a/Swift/QtUI/QtHighlightEditorWidget.ui b/Swift/QtUI/QtHighlightEditorWidget.ui
new file mode 100644
index 0000000..0f39168
--- /dev/null
+++ b/Swift/QtUI/QtHighlightEditorWidget.ui
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtHighlightEditorWidget</class>
+ <widget class="QWidget" name="QtHighlightEditorWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>419</width>
+ <height>373</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label">
+ <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="QTreeView" name="treeView">
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Swift::QtHighlightRuleWidget" name="ruleWidget" native="true"/>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QPushButton" name="newButton">
+ <property name="text">
+ <string>New</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="deleteButton">
+ <property name="text">
+ <string>Delete</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </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>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="closeButton">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>Swift::QtHighlightRuleWidget</class>
+ <extends>QWidget</extends>
+ <header>QtHighlightRuleWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/Swift/QtUI/QtHighlightRuleWidget.cpp b/Swift/QtUI/QtHighlightRuleWidget.cpp
new file mode 100644
index 0000000..9c0df5e
--- /dev/null
+++ b/Swift/QtUI/QtHighlightRuleWidget.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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
new file mode 100644
index 0000000..8a59a14
--- /dev/null
+++ b/Swift/QtUI/QtHighlightRuleWidget.h
@@ -0,0 +1,49 @@
+/*
+ * 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
new file mode 100644
index 0000000..9c465f9
--- /dev/null
+++ b/Swift/QtUI/QtHighlightRuleWidget.ui
@@ -0,0 +1,260 @@
+<?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>&amp;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>&amp;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>&amp;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 &amp;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 &amp;words</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="1">
+ <widget class="QCheckBox" name="matchCase">
+ <property name="text">
+ <string>Match &amp;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>&amp;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&amp;olors:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Swift::QtColorToolButton" name="foreground">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&amp;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>&amp;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>&amp;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&amp;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/QtHighlightRulesItemModel.cpp b/Swift/QtUI/QtHighlightRulesItemModel.cpp
new file mode 100644
index 0000000..ff2f639
--- /dev/null
+++ b/Swift/QtUI/QtHighlightRulesItemModel.cpp
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <boost/algorithm/string.hpp>
+#include <boost/lambda/lambda.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+
+#include <QStringList>
+#include <QColor>
+
+#include <Swift/Controllers/HighlightManager.h>
+#include <Swift/QtUI/QtHighlightRulesItemModel.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
+
+namespace Swift {
+
+QtHighlightRulesItemModel::QtHighlightRulesItemModel(QObject* parent) : QAbstractItemModel(parent), highlightManager_(NULL)
+{
+}
+
+void QtHighlightRulesItemModel::setHighlightManager(HighlightManager* highlightManager)
+{
+ emit layoutAboutToBeChanged();
+ highlightManager_ = highlightManager;
+ emit layoutChanged();
+}
+
+QVariant QtHighlightRulesItemModel::headerData(int section, Qt::Orientation /* orientation */, int role) const
+{
+ if (role == Qt::DisplayRole) {
+ switch (section) {
+ case ApplyTo: return QVariant(tr("Apply to"));
+ case Sender: return QVariant(tr("Sender"));
+ case Keyword: return QVariant(tr("Keyword"));
+ case Action: return QVariant(tr("Action"));
+ case NickIsKeyword: return QVariant(tr("Nick Is Keyword"));
+ case MatchCase: return QVariant(tr("Match Case"));
+ case MatchWholeWords: return QVariant(tr("Match Whole Words"));
+ case HighlightText: return QVariant(tr("Highlight Text"));
+ case TextColor: return QVariant(tr("Text Color"));
+ case TextBackground: return QVariant(tr("Text Background"));
+ case PlaySound: return QVariant(tr("Play Sounds"));
+ case SoundFile: return QVariant(tr("Sound File"));
+ }
+ }
+
+ return QVariant();
+}
+
+int QtHighlightRulesItemModel::columnCount(const QModelIndex& /* parent */) const
+{
+ return NumberOfColumns;
+}
+
+QVariant QtHighlightRulesItemModel::data(const QModelIndex &index, int role) const
+{
+ if (index.isValid() && highlightManager_ && (role == Qt::DisplayRole || role == Qt::EditRole)) {
+
+ const char* separator = (role == Qt::DisplayRole) ? " ; " : "\n";
+
+ if (boost::numeric_cast<std::vector<std::string>::size_type>(index.row()) < highlightManager_->getRules().size()) {
+ const HighlightRule& r = highlightManager_->getRules()[index.row()];
+ switch (index.column()) {
+ case ApplyTo: {
+ int applyTo = 0;
+ if (r.getMatchChat() && r.getMatchMUC()) {
+ applyTo = 1;
+ } else if (r.getMatchChat()) {
+ applyTo = 2;
+ } else if (r.getMatchMUC()) {
+ applyTo = 3;
+ }
+
+ if (role == Qt::DisplayRole) {
+ return QVariant(getApplyToString(applyTo));
+ } else {
+ return QVariant(applyTo);
+ }
+ }
+ case Sender: {
+ std::string s = boost::join(r.getSenders(), separator);
+ return QVariant(P2QSTRING(s));
+ }
+ case Keyword: {
+ std::string s = boost::join(r.getKeywords(), separator);
+ QString qs(P2QSTRING(s));
+ if (role == Qt::DisplayRole && r.getNickIsKeyword()) {
+ qs = tr("<nick>") + (qs.isEmpty() ? "" : separator + qs);
+ }
+ return QVariant(qs);
+ }
+ case Action: {
+ std::vector<std::string> v;
+ const HighlightAction & action = r.getAction();
+ if (action.highlightText()) {
+ v.push_back(Q2PSTRING(tr("Highlight text")));
+ }
+ if (action.playSound()) {
+ v.push_back(Q2PSTRING(tr("Play sound")));
+ }
+ std::string s = boost::join(v, separator);
+ return QVariant(P2QSTRING(s));
+ }
+ case NickIsKeyword: {
+ return QVariant(r.getNickIsKeyword());
+ }
+ case MatchCase: {
+ return QVariant(r.getMatchCase());
+ }
+ case MatchWholeWords: {
+ return QVariant(r.getMatchWholeWords());
+ }
+ case HighlightText: {
+ return QVariant(r.getAction().highlightText());
+ }
+ case TextColor: {
+ return QVariant(QColor(P2QSTRING(r.getAction().getTextColor())));
+ }
+ case TextBackground: {
+ return QVariant(QColor(P2QSTRING(r.getAction().getTextBackground())));
+ }
+ case PlaySound: {
+ return QVariant(r.getAction().playSound());
+ }
+ case SoundFile: {
+ return QVariant(P2QSTRING(r.getAction().getSoundFile()));
+ }
+ }
+ }
+ }
+ return QVariant();
+}
+
+bool QtHighlightRulesItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (index.isValid() && highlightManager_ && role == Qt::EditRole) {
+ if (boost::numeric_cast<std::vector<std::string>::size_type>(index.row()) < highlightManager_->getRules().size()) {
+ HighlightRule r = highlightManager_->getRule(index.row());
+ std::vector<int> changedColumns;
+ switch (index.column()) {
+ case ApplyTo: {
+ bool ok = false;
+ int applyTo = value.toInt(&ok);
+ if (!ok) {
+ return false;
+ }
+ r.setMatchChat(applyTo == ApplyToAll || applyTo == ApplyToChat);
+ r.setMatchMUC(applyTo == ApplyToAll || applyTo == ApplyToMUC);
+ break;
+ }
+ case Sender:
+ case Keyword: {
+ std::string s = Q2PSTRING(value.toString());
+ std::vector<std::string> v;
+ boost::split(v, s, boost::is_any_of("\n"));
+ v.erase(std::remove_if(v.begin(), v.end(), boost::lambda::_1 == ""), v.end());
+ if (index.column() == Sender) {
+ r.setSenders(v);
+ } else {
+ r.setKeywords(v);
+ }
+ break;
+ }
+ case NickIsKeyword: {
+ r.setNickIsKeyword(value.toBool());
+ changedColumns.push_back(Keyword); // "<nick>"
+ break;
+ }
+ case MatchCase: {
+ r.setMatchCase(value.toBool());
+ break;
+ }
+ case MatchWholeWords: {
+ r.setMatchWholeWords(value.toBool());
+ break;
+ }
+ case HighlightText: {
+ r.getAction().setHighlightText(value.toBool());
+ changedColumns.push_back(Action);
+ break;
+ }
+ case TextColor: {
+ QColor c = value.value<QColor>();
+ r.getAction().setTextColor(c.isValid() ? Q2PSTRING(c.name()) : "");
+ break;
+ }
+ case TextBackground: {
+ QColor c = value.value<QColor>();
+ r.getAction().setTextBackground(c.isValid() ? Q2PSTRING(c.name()) : "");
+ break;
+ }
+ case PlaySound: {
+ r.getAction().setPlaySound(value.toBool());
+ changedColumns.push_back(Action);
+ break;
+ }
+ case SoundFile: {
+ r.getAction().setSoundFile(Q2PSTRING(value.toString()));
+ break;
+ }
+ }
+
+ highlightManager_->setRule(index.row(), r);
+ emit dataChanged(index, index);
+ foreach (int column, changedColumns) {
+ QModelIndex i = createIndex(index.row(), column, 0);
+ emit dataChanged(i, i);
+ }
+ }
+ }
+
+ return false;
+}
+
+QModelIndex QtHighlightRulesItemModel::parent(const QModelIndex& /* child */) const
+{
+ return QModelIndex();
+}
+
+int QtHighlightRulesItemModel::rowCount(const QModelIndex& /* parent */) const
+{
+ return highlightManager_ ? highlightManager_->getRules().size() : 0;
+}
+
+QModelIndex QtHighlightRulesItemModel::index(int row, int column, const QModelIndex& /* parent */) const
+{
+ return createIndex(row, column, 0);
+}
+
+bool QtHighlightRulesItemModel::insertRows(int row, int count, const QModelIndex& /* parent */)
+{
+ if (highlightManager_) {
+ beginInsertRows(QModelIndex(), row, row + count);
+ while (count--) {
+ highlightManager_->insertRule(row, HighlightRule());
+ }
+ endInsertRows();
+ return true;
+ }
+ return false;
+}
+
+bool QtHighlightRulesItemModel::removeRows(int row, int count, const QModelIndex& /* parent */)
+{
+ if (highlightManager_) {
+ beginRemoveRows(QModelIndex(), row, row + count);
+ while (count--) {
+ highlightManager_->removeRule(row);
+ }
+ endRemoveRows();
+ return true;
+ }
+ return false;
+}
+
+bool QtHighlightRulesItemModel::swapRows(int row1, int row2)
+{
+ if (highlightManager_) {
+ assert(row1 >= 0 && row2 >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(row1) < highlightManager_->getRules().size() && boost::numeric_cast<std::vector<std::string>::size_type>(row2) < highlightManager_->getRules().size());
+ HighlightRule r = highlightManager_->getRule(row1);
+ highlightManager_->setRule(row1, highlightManager_->getRule(row2));
+ highlightManager_->setRule(row2, r);
+ emit dataChanged(index(row1, 0, QModelIndex()), index(row1, 0, QModelIndex()));
+ emit dataChanged(index(row2, 0, QModelIndex()), index(row2, 0, QModelIndex()));
+ return true;
+ }
+ return false;
+}
+
+QString QtHighlightRulesItemModel::getApplyToString(int applyTo)
+{
+ switch (applyTo) {
+ case ApplyToNone: return tr("None");
+ case ApplyToAll: return tr("Chat or MUC");
+ case ApplyToChat: return tr("Chat");
+ case ApplyToMUC: return tr("MUC");
+ default: return "";
+ }
+}
+
+}
diff --git a/Swift/QtUI/QtHighlightRulesItemModel.h b/Swift/QtUI/QtHighlightRulesItemModel.h
new file mode 100644
index 0000000..ac85628
--- /dev/null
+++ b/Swift/QtUI/QtHighlightRulesItemModel.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QAbstractItemModel>
+
+namespace Swift {
+
+ class HighlightManager;
+
+ class QtHighlightRulesItemModel : public QAbstractItemModel {
+ Q_OBJECT
+
+ public:
+ QtHighlightRulesItemModel(QObject* parent = NULL);
+
+ void setHighlightManager(HighlightManager* highlightManager);
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+ int columnCount(const QModelIndex& parent) const;
+ QVariant data(const QModelIndex& index, int role) const;
+ bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);
+ QModelIndex parent(const QModelIndex& child) const;
+ int rowCount(const QModelIndex& parent) const;
+ QModelIndex index(int row, int column, const QModelIndex& parent) const;
+ bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex());
+ bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
+ bool swapRows(int row1, int row2);
+
+ static QString getApplyToString(int);
+
+ enum Columns {
+ ApplyTo = 0,
+ Sender,
+ Keyword,
+ Action,
+ NickIsKeyword,
+ MatchCase,
+ MatchWholeWords,
+ HighlightText,
+ TextColor,
+ TextBackground,
+ PlaySound,
+ SoundFile,
+ NumberOfColumns // end of list marker
+ };
+
+ enum ApplyToValues {
+ ApplyToNone = 0,
+ ApplyToAll,
+ ApplyToChat,
+ ApplyToMUC,
+ ApplyToEOL // end of list marker
+ };
+
+ private:
+ HighlightManager* highlightManager_;
+ };
+
+}
diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp
index c27edfb..cf22ad0 100644
--- a/Swift/QtUI/QtLoginWindow.cpp
+++ b/Swift/QtUI/QtLoginWindow.cpp
@@ -30,6 +30,7 @@
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIEvents/RequestXMLConsoleUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h>
#include <Swift/Controllers/Settings/SettingsProvider.h>
#include <Swift/Controllers/SettingConstants.h>
#include <Swift/QtUI/QtUISettingConstants.h>
@@ -190,6 +191,10 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* set
generalMenu_->addAction(fileTransferOverviewAction_);
#endif
+ highlightEditorAction_ = new QAction(tr("&Edit Highlight Rules"), this);
+ connect(highlightEditorAction_, SIGNAL(triggered()), SLOT(handleShowHighlightEditor()));
+ generalMenu_->addAction(highlightEditorAction_);
+
toggleSoundsAction_ = new QAction(tr("&Play Sounds"), this);
toggleSoundsAction_->setCheckable(true);
toggleSoundsAction_->setChecked(settings_->getSetting(SettingConstants::PLAY_SOUNDS));
@@ -438,6 +443,10 @@ void QtLoginWindow::handleShowFileTransferOverview() {
uiEventStream_->send(boost::make_shared<RequestFileTransferListUIEvent>());
}
+void QtLoginWindow::handleShowHighlightEditor() {
+ uiEventStream_->send(boost::make_shared<RequestHighlightEditorUIEvent>());
+}
+
void QtLoginWindow::handleToggleSounds(bool enabled) {
settings_->storeSetting(SettingConstants::PLAY_SOUNDS, enabled);
}
diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h
index c1966d8..7415fbf 100644
--- a/Swift/QtUI/QtLoginWindow.h
+++ b/Swift/QtUI/QtLoginWindow.h
@@ -62,6 +62,7 @@ namespace Swift {
void handleQuit();
void handleShowXMLConsole();
void handleShowFileTransferOverview();
+ void handleShowHighlightEditor();
void handleToggleSounds(bool enabled);
void handleToggleNotifications(bool enabled);
void handleAbout();
@@ -103,6 +104,7 @@ namespace Swift {
SettingsProvider* settings_;
QAction* xmlConsoleAction_;
QAction* fileTransferOverviewAction_;
+ QAction* highlightEditorAction_;
TimerFactory* timerFactory_;
ClientOptions currentOptions_;
};
diff --git a/Swift/QtUI/QtSoundPlayer.cpp b/Swift/QtUI/QtSoundPlayer.cpp
index 387c6f3..63f76f0 100644
--- a/Swift/QtUI/QtSoundPlayer.cpp
+++ b/Swift/QtUI/QtSoundPlayer.cpp
@@ -16,10 +16,11 @@ namespace Swift {
QtSoundPlayer::QtSoundPlayer(ApplicationPathProvider* applicationPathProvider) : applicationPathProvider(applicationPathProvider) {
}
-void QtSoundPlayer::playSound(SoundEffect sound) {
+void QtSoundPlayer::playSound(SoundEffect sound, const std::string& soundResource) {
+
switch (sound) {
case MessageReceived:
- playSound("/sounds/message-received.wav");
+ playSound(soundResource.empty() ? "/sounds/message-received.wav" : soundResource);
break;
}
}
@@ -29,6 +30,9 @@ void QtSoundPlayer::playSound(const std::string& soundResource) {
if (boost::filesystem::exists(resourcePath)) {
QSound::play(resourcePath.string().c_str());
}
+ else if (boost::filesystem::exists(soundResource)) {
+ QSound::play(soundResource.c_str());
+ }
else {
std::cerr << "Unable to find sound: " << soundResource << std::endl;
}
diff --git a/Swift/QtUI/QtSoundPlayer.h b/Swift/QtUI/QtSoundPlayer.h
index 6945f45..f8da392 100644
--- a/Swift/QtUI/QtSoundPlayer.h
+++ b/Swift/QtUI/QtSoundPlayer.h
@@ -19,7 +19,7 @@ namespace Swift {
public:
QtSoundPlayer(ApplicationPathProvider* applicationPathProvider);
- void playSound(SoundEffect sound);
+ void playSound(SoundEffect sound, const std::string& soundResource);
private:
void playSound(const std::string& soundResource);
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index 008d042..2ec2818 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -25,6 +25,7 @@
#include "QtContactEditWindow.h"
#include "QtAdHocCommandWindow.h"
#include "QtFileTransferListWidget.h"
+#include <QtHighlightEditorWidget.h>
#include "Whiteboard/QtWhiteboardWindow.h"
#include <Swift/Controllers/Settings/SettingsProviderHierachy.h>
#include <Swift/QtUI/QtUISettingConstants.h>
@@ -162,6 +163,10 @@ WhiteboardWindow* QtUIFactory::createWhiteboardWindow(boost::shared_ptr<Whiteboa
return new QtWhiteboardWindow(whiteboardSession);
}
+HighlightEditorWidget* QtUIFactory::createHighlightEditorWidget() {
+ return new QtHighlightEditorWidget();
+}
+
void QtUIFactory::createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) {
new QtAdHocCommandWindow(command);
}
diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h
index 4cf91ca..a1baa82 100644
--- a/Swift/QtUI/QtUIFactory.h
+++ b/Swift/QtUI/QtUIFactory.h
@@ -48,6 +48,7 @@ namespace Swift {
virtual ContactEditWindow* createContactEditWindow();
virtual FileTransferListWidget* createFileTransferListWidget();
virtual WhiteboardWindow* createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession);
+ virtual HighlightEditorWidget* createHighlightEditorWidget();
virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command);
private slots:
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 607a8a6..cd0ed57 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -113,6 +113,10 @@ sources = [
"QtContactEditWindow.cpp",
"QtContactEditWidget.cpp",
"QtSingleWindow.cpp",
+ "QtHighlightEditorWidget.cpp",
+ "QtHighlightRulesItemModel.cpp",
+ "QtHighlightRuleWidget.cpp",
+ "QtColorToolButton.cpp",
"ChatSnippet.cpp",
"MessageSnippet.cpp",
"SystemMessageSnippet.cpp",
@@ -231,6 +235,8 @@ 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.Qrc("DefaultTheme.qrc")
myenv.Qrc("Swift.qrc")