summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTobias Markmann <tm@ayena.de>2017-01-31 14:57:22 (GMT)
committerEdwin Mons <edwin.mons@isode.com>2017-02-27 14:07:13 (GMT)
commitfc8f5b31c22ed7af4f0e2473f269601a87a0438c (patch)
tree0c59a9debf72247c7409947a3a4cccb6c616dd06 /Swift/Controllers/Chat/MUCController.cpp
parentabd81d4a3cf08ffaa1e5265d204cdd80c8c0583b (diff)
downloadswift-fc8f5b31c22ed7af4f0e2473f269601a87a0438c.zip
swift-fc8f5b31c22ed7af4f0e2473f269601a87a0438c.tar.bz2
Redesign highlight logic and processing
The new highlight logic follows a simpler model. It supports: * highlighting of whole words in a message * highlighting messages by sender name * highlighting if the user’s name is mentioned Possible actions for these highlights are text colouring, sound playback of WAV files, and system notifications. In addition the user can decide to receive sound and system notification on general incoming direct and group messages. Redesigned the highlight configuration UI dialog for this new model. ChatMessageParser class now deals with all parsing and marking up the chat message with the matching HighlightActions. Highlighter class has been extended to deal with all sound and system notification highlights that should be emitted by a specified chat message. Moved some tests over to gtest in the process. Test-Information: Tested UI on macOS 10.12.3 with Qt 5.7.1. Manually tested that correct system notification are emitted on mentions, keyword highlights and general messages. Added new unit tests to cover new highlighting behaviour. Change-Id: I1c89e29d81022174187fb44af0d384036ec51594
Diffstat (limited to 'Swift/Controllers/Chat/MUCController.cpp')
-rw-r--r--Swift/Controllers/Chat/MUCController.cpp13
1 files changed, 7 insertions, 6 deletions
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index df54d73..c476cf3 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -7,162 +7,162 @@
#include <Swift/Controllers/Chat/MUCController.h>
#include <algorithm>
#include <memory>
#include <boost/bind.hpp>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Base/format.h>
#include <Swiften/Base/Tristate.h>
#include <Swiften/Client/BlockList.h>
#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/Client/StanzaChannel.h>
#include <Swiften/Disco/EntityCapsProvider.h>
#include <Swiften/Elements/Delay.h>
#include <Swiften/Elements/Thread.h>
#include <Swiften/MUC/MUC.h>
#include <Swiften/MUC/MUCBookmark.h>
#include <Swiften/MUC/MUCBookmarkManager.h>
#include <Swiften/Network/Timer.h>
#include <Swiften/Network/TimerFactory.h>
#include <Swiften/Roster/XMPPRoster.h>
#include <SwifTools/TabComplete.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
-#include <Swift/Controllers/Highlighter.h>
+#include <Swift/Controllers/Highlighting/Highlighter.h>
#include <Swift/Controllers/Intl.h>
#include <Swift/Controllers/Roster/ContactRosterItem.h>
#include <Swift/Controllers/Roster/GroupRosterItem.h>
#include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h>
#include <Swift/Controllers/Roster/ItemOperations/SetMUC.h>
#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h>
#include <Swift/Controllers/Roster/Roster.h>
#include <Swift/Controllers/Roster/RosterVCardProvider.h>
#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>
#define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000
namespace Swift {
class MUCBookmarkPredicate {
public:
MUCBookmarkPredicate(const JID& mucJID) : roomJID_(mucJID) { }
bool operator()(const MUCBookmark& operand) {
return operand.getRoom() == roomJID_;
}
private:
JID roomJID_;
};
/**
* The controller does not gain ownership of the stanzaChannel, nor the factory.
*/
MUCController::MUCController (
const JID& self,
MUC::ref muc,
const boost::optional<std::string>& password,
const std::string &nick,
StanzaChannel* stanzaChannel,
IQRouter* iqRouter,
ChatWindowFactory* chatWindowFactory,
+ NickResolver* nickResolver,
PresenceOracle* presenceOracle,
AvatarManager* avatarManager,
UIEventStream* uiEventStream,
bool useDelayForLatency,
TimerFactory* timerFactory,
EventController* eventController,
EntityCapsProvider* entityCapsProvider,
XMPPRoster* xmppRoster,
HistoryController* historyController,
MUCRegistry* mucRegistry,
HighlightManager* highlightManager,
ClientBlockListManager* clientBlockListManager,
std::shared_ptr<ChatMessageParser> chatMessageParser,
bool isImpromptu,
AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider,
VCardManager* vcardManager,
MUCBookmarkManager* mucBookmarkManager) :
- ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false), clientBlockListManager_(clientBlockListManager), mucBookmarkManager_(mucBookmarkManager) {
+ ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), nickResolver, presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false), clientBlockListManager_(clientBlockListManager), mucBookmarkManager_(mucBookmarkManager) {
parting_ = true;
joined_ = false;
lastWasPresence_ = false;
shouldJoinOnReconnect_ = true;
doneGettingHistory_ = false;
xmppRoster_ = xmppRoster;
subject_ = "";
isInitialJoin_ = true;
roster_ = std::unique_ptr<Roster>(new Roster(false, true));
rosterVCardProvider_ = new RosterVCardProvider(roster_.get(), vcardManager, JID::WithResource);
completer_ = new TabComplete();
chatWindow_->setRosterModel(roster_.get());
chatWindow_->setTabComplete(completer_);
chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this));
chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1));
chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2));
chatWindow_->onChangeSubjectRequest.connect(boost::bind(&MUCController::handleChangeSubjectRequest, this, _1));
chatWindow_->onBookmarkRequest.connect(boost::bind(&MUCController::handleBookmarkRequest, this));
chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1));
chatWindow_->onConfigurationFormCancelled.connect(boost::bind(&MUCController::handleConfigurationCancelled, this));
chatWindow_->onDestroyRequest.connect(boost::bind(&MUCController::handleDestroyRoomRequest, this));
chatWindow_->onInviteToChat.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this, _1));
chatWindow_->onGetAffiliationsRequest.connect(boost::bind(&MUCController::handleGetAffiliationsRequest, this));
chatWindow_->onChangeAffiliationsRequest.connect(boost::bind(&MUCController::handleChangeAffiliationsRequest, this, _1));
chatWindow_->onUnblockUserRequest.connect(boost::bind(&MUCController::handleUnblockUserRequest, this));
muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1));
muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1));
muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1));
muc_->onOccupantNicknameChanged.connect(boost::bind(&MUCController::handleOccupantNicknameChanged, this, _1, _2));
muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1));
muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3));
muc_->onRoleChangeFailed.connect(boost::bind(&MUCController::handleOccupantRoleChangeFailed, this, _1, _2, _3));
muc_->onAffiliationListReceived.connect(boost::bind(&MUCController::handleAffiliationListReceived, this, _1, _2));
muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1));
muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1));
- highlighter_->setMode(isImpromptu_ ? Highlighter::ChatMode : Highlighter::MUCMode);
- highlighter_->setNick(nick_);
+ chatMessageParser_->setNick(nick_);
if (timerFactory && stanzaChannel_->isAvailable()) {
loginCheckTimer_ = std::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS));
loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this));
loginCheckTimer_->start();
}
else {
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "You are currently offline. You will enter this room when you are connected.")), ChatWindow::DefaultDirection);
}
if (isImpromptu) {
muc_->onUnlocked.connect(boost::bind(&MUCController::handleRoomUnlocked, this));
chatWindow_->convertToMUC(ChatWindow::ImpromptuMUC);
} else {
muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3));
muc_->onOccupantAffiliationChanged.connect(boost::bind(&MUCController::handleOccupantAffiliationChanged, this, _1, _2, _3));
chatWindow_->convertToMUC(ChatWindow::StandardMUC);
chatWindow_->setName(muc->getJID().getNode());
}
if (stanzaChannel->isAvailable()) {
MUCController::setOnline(true);
}
if (avatarManager_ != nullptr) {
avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1)));
}
MUCController::handleBareJIDCapsChanged(muc->getJID());
eventStream_->onUIEvent.connect(boost::bind(&MUCController::handleUIEvent, this, _1));
// setup handling of MUC bookmark changes
mucBookmarkManagerBookmarkAddedConnection_ = (mucBookmarkManager_->onBookmarkAdded.connect(boost::bind(&MUCController::handleMUCBookmarkAdded, this, _1)));
mucBookmarkManagerBookmarkRemovedConnection_ = (mucBookmarkManager_->onBookmarkRemoved.connect(boost::bind(&MUCController::handleMUCBookmarkRemoved, this, _1)));
@@ -566,64 +566,65 @@ void MUCController::preHandleIncomingMessage(std::shared_ptr<MessageEvent> messa
}
isInitialJoin_ = false;
chatWindow_->setSubject(message->getSubject());
doneGettingHistory_ = true;
subject_ = message->getSubject();
}
if (!doneGettingHistory_ && !message->getPayload<Delay>()) {
doneGettingHistory_ = true;
}
if (!doneGettingHistory_) {
checkDuplicates(message);
messageEvent->conclude();
}
}
void MUCController::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) {
if (from.isBare()) {
chatWindow_->addSystemMessage(message, ChatWindow::DefaultDirection);
}
else {
ChatControllerBase::addMessageHandleIncomingMessage(from, message, senderIsSelf, label, time);
}
}
void MUCController::postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) {
std::shared_ptr<Message> message = messageEvent->getStanza();
if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && !message->getPayload<Delay>()) {
if (messageTargetsMe(message) || isImpromptu_) {
+ highlighter_->handleSystemNotifications(chatMessage, messageEvent);
eventController_->handleIncomingEvent(messageEvent);
}
if (!messageEvent->getConcluded()) {
- handleHighlightActions(chatMessage);
+ highlighter_->handleSoundNotifications(chatMessage);
}
}
}
void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant, const MUCOccupant::Role& oldRole) {
clearPresenceQueue();
receivedActivity();
JID jid(nickToJID(nick));
roster_->removeContactFromGroup(jid, roleToGroupName(oldRole));
JID realJID;
if (occupant.getRealJID()) {
realJID = occupant.getRealJID().get();
}
std::string group(roleToGroupName(occupant.getRole()));
roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid));
roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole()));
roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation()));
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection);
if (nick == nick_) {
setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole());
}
}
void MUCController::handleOccupantAffiliationChanged(const std::string& nick, const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Affiliation& /*oldAffiliation*/)
{
if (nick == nick_) {
setAvailableRoomActions(affiliation, muc_->getOccupant(nick_).getRole());
}
JID jid(nickToJID(nick));
MUCOccupant occupant = muc_->getOccupant(nick);
@@ -1084,61 +1085,61 @@ void MUCController::addRecentLogs() {
}
}
void MUCController::checkDuplicates(std::shared_ptr<Message> newMessage) {
std::string body = newMessage->getBody().get_value_or("");
JID jid = newMessage->getFrom();
boost::optional<boost::posix_time::ptime> time = newMessage->getTimestamp();
for (const auto& message : boost::adaptors::reverse(joinContext_)) {
boost::posix_time::ptime messageTime = message.getTime() - boost::posix_time::hours(message.getOffset());
if (time && time < messageTime) {
break;
}
if (time && time != messageTime) {
continue;
}
if (message.getFromJID() != jid) {
continue;
}
if (message.getMessage() != body) {
continue;
}
// Mark the message as unreadable
newMessage->setBody("");
}
}
void MUCController::setNick(const std::string& nick) {
nick_ = nick;
- highlighter_->setNick(nick_);
+ chatMessageParser_->setNick(nick);
}
Form::ref MUCController::buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm) {
Form::ref result = std::make_shared<Form>(Form::SubmitType);
std::string impromptuConfigs[] = { "muc#roomconfig_enablelogging", "muc#roomconfig_persistentroom", "muc#roomconfig_publicroom", "muc#roomconfig_whois"};
std::set<std::string> impromptuConfigsMissing(impromptuConfigs, impromptuConfigs + 4);
for (const auto& field : roomConfigurationForm->getFields()) {
std::shared_ptr<FormField> resultField;
if (field->getName() == "muc#roomconfig_enablelogging") {
resultField = std::make_shared<FormField>(FormField::BooleanType, "0");
}
if (field->getName() == "muc#roomconfig_persistentroom") {
resultField = std::make_shared<FormField>(FormField::BooleanType, "0");
}
if (field->getName() == "muc#roomconfig_publicroom") {
resultField = std::make_shared<FormField>(FormField::BooleanType, "0");
}
if (field->getName() == "muc#roomconfig_whois") {
resultField = std::make_shared<FormField>(FormField::ListSingleType, "anyone");
}
if (field->getName() == "FORM_TYPE") {
resultField = std::make_shared<FormField>(FormField::HiddenType, "http://jabber.org/protocol/muc#roomconfig");
}
if (resultField) {
impromptuConfigsMissing.erase(field->getName());
resultField->setName(field->getName());
result->addField(resultField);
}