summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.cpp4
-rw-r--r--Swift/Controllers/Chat/ChatMessageParser.cpp9
-rw-r--r--Swift/Controllers/Chat/ChatMessageParser.h4
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp36
-rw-r--r--Swift/Controllers/HighlightAction.cpp2
-rw-r--r--Swift/Controllers/HighlightAction.h11
-rw-r--r--Swift/Controllers/HighlightRule.cpp12
-rw-r--r--Swift/Controllers/HighlightRule.h2
-rw-r--r--Swift/Controllers/Highlighter.h1
-rw-r--r--Swift/QtUI/QtHighlightEditor.cpp45
-rw-r--r--Swift/QtUI/QtHighlightEditor.ui7
-rw-r--r--Swift/QtUI/QtWebKitChatView.cpp8
12 files changed, 88 insertions, 53 deletions
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 24341e6..519deda 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -1,373 +1,373 @@
/*
* Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <Swift/Controllers/Chat/ChatControllerBase.h>
#include <sstream>
#include <map>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <boost/algorithm/string.hpp>
#include <Swiften/Base/format.h>
#include <Swiften/Base/Path.h>
#include <Swiften/Base/String.h>
#include <Swiften/Client/StanzaChannel.h>
#include <Swiften/Elements/Delay.h>
#include <Swiften/Elements/MUCInvitationPayload.h>
#include <Swiften/Elements/MUCUserPayload.h>
#include <Swiften/Base/foreach.h>
#include <Swiften/Disco/EntityCapsProvider.h>
#include <Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h>
#include <Swiften/Avatars/AvatarManager.h>
#include <Swift/Controllers/Intl.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>
#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h>
#include <Swift/Controllers/HighlightManager.h>
#include <Swift/Controllers/Highlighter.h>
#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
namespace Swift {
ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, boost::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) {
chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);
chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
chatWindow_->onLogCleared.connect(boost::bind(&ChatControllerBase::handleLogCleared, this));
entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1));
highlighter_ = highlightManager->createHighlighter();
setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable());
createDayChangeTimer();
}
ChatControllerBase::~ChatControllerBase() {
delete chatWindow_;
}
void ChatControllerBase::handleLogCleared() {
cancelReplaces();
}
ChatWindow* ChatControllerBase::detachChatWindow() {
ChatWindow* chatWindow = chatWindow_;
chatWindow_ = NULL;
return chatWindow;
}
void ChatControllerBase::handleCapsChanged(const JID& jid) {
if (jid.compare(toJID_, JID::WithoutResource) == 0) {
handleBareJIDCapsChanged(jid);
}
}
void ChatControllerBase::setCanStartImpromptuChats(bool supportsImpromptu) {
if (chatWindow_) {
chatWindow_->setCanInitiateImpromptuChats(supportsImpromptu);
}
}
void ChatControllerBase::createDayChangeTimer() {
if (timerFactory_) {
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
boost::posix_time::ptime midnight(now.date() + boost::gregorian::days(1));
int millisecondsUntilMidnight = boost::numeric_cast<int>((midnight - now).total_milliseconds());
dateChangeTimer_ = boost::shared_ptr<Timer>(timerFactory_->createTimer(millisecondsUntilMidnight));
dateChangeTimer_->onTick.connect(boost::bind(&ChatControllerBase::handleDayChangeTick, this));
dateChangeTimer_->start();
}
}
void ChatControllerBase::handleDayChangeTick() {
dateChangeTimer_->stop();
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))), ChatWindow::DefaultDirection);
dayTicked();
createDayChangeTimer();
}
void ChatControllerBase::setEnabled(bool enabled) {
chatWindow_->setInputEnabled(enabled);
}
void ChatControllerBase::setOnline(bool online) {
setEnabled(online);
}
JID ChatControllerBase::getBaseJID() {
return JID(toJID_.toBare());
}
void ChatControllerBase::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) {
if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::SecurityLabelsCatalogFeature)) {
GetSecurityLabelsCatalogRequest::ref request = GetSecurityLabelsCatalogRequest::create(getBaseJID(), iqRouter_);
request->onResponse.connect(boost::bind(&ChatControllerBase::handleSecurityLabelsCatalogResponse, this, _1, _2));
request->send();
} else {
chatWindow_->setSecurityLabelsEnabled(false);
labelsEnabled_ = false;
}
}
void ChatControllerBase::handleAllMessagesRead() {
if (!unreadMessages_.empty()) {
targetedUnreadMessages_.clear();
foreach (boost::shared_ptr<StanzaEvent> stanzaEvent, unreadMessages_) {
stanzaEvent->conclude();
}
unreadMessages_.clear();
chatWindow_->setUnreadMessageCount(0);
onUnreadCountChanged();
}
}
int ChatControllerBase::getUnreadCount() {
return boost::numeric_cast<int>(targetedUnreadMessages_.size());
}
void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool isCorrectionMessage) {
if (!stanzaChannel_->isAvailable() || body.empty()) {
return;
}
boost::shared_ptr<Message> message(new Message());
message->setTo(toJID_);
message->setType(Swift::Message::Chat);
message->setBody(body);
if (labelsEnabled_) {
if (!isCorrectionMessage) {
lastLabel_ = chatWindow_->getSelectedSecurityLabel();
}
SecurityLabelsCatalog::Item labelItem = lastLabel_;
if (labelItem.getLabel()) {
message->addPayload(labelItem.getLabel());
}
}
preSendMessageRequest(message);
boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
if (useDelayForLatency_) {
message->addPayload(boost::make_shared<Delay>(now, selfJID_));
}
if (isCorrectionMessage) {
message->addPayload(boost::shared_ptr<Replace> (new Replace(lastSentMessageStanzaID_)));
}
message->setID(lastSentMessageStanzaID_ = idGenerator_.generateID());
stanzaChannel_->sendMessage(message);
postSendMessage(message->getBody(), boost::dynamic_pointer_cast<Stanza>(message));
onActivity(message->getBody());
#ifdef SWIFT_EXPERIMENTAL_HISTORY
logMessage(body, selfJID_, toJID_, now, false);
#endif
}
void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) {
if (catalog && !error) {
if (catalog->getItems().size() == 0) {
chatWindow_->setSecurityLabelsEnabled(false);
labelsEnabled_ = false;
} else {
labelsEnabled_ = true;
chatWindow_->setAvailableSecurityLabels(catalog->getItems());
chatWindow_->setSecurityLabelsEnabled(true);
}
} else {
labelsEnabled_ = false;
chatWindow_->setSecurityLabelsError();
}
}
void ChatControllerBase::showChatWindow() {
chatWindow_->show();
}
void ChatControllerBase::activateChatWindow() {
chatWindow_->activate();
}
bool ChatControllerBase::hasOpenWindow() const {
return chatWindow_ && chatWindow_->isVisible();
}
std::string ChatControllerBase::addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
if (boost::starts_with(message, "/me ")) {
return chatWindow_->addAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
} else {
- return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message,senderIsSelf), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
+ return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message,highlighter_->getNick(),senderIsSelf), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
}
}
void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, bool senderIsSelf, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
if (boost::starts_with(message, "/me ")) {
chatWindow_->replaceWithAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), id, time, highlight);
} else {
- chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message,senderIsSelf), id, time, highlight);
+ chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message,highlighter_->getNick(),senderIsSelf), id, time, highlight);
}
}
bool ChatControllerBase::isFromContact(const JID& from) {
return from.toBare() == toJID_.toBare();
}
void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
preHandleIncomingMessage(messageEvent);
if (messageEvent->isReadable() && !messageEvent->getConcluded()) {
unreadMessages_.push_back(messageEvent);
if (messageEvent->targetsMe()) {
targetedUnreadMessages_.push_back(messageEvent);
}
}
boost::shared_ptr<Message> message = messageEvent->getStanza();
std::string body = message->getBody();
HighlightAction highlight;
if (message->isError()) {
if (!message->getTo().getResource().empty()) {
std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>()));
chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
}
}
else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) {
handleMUCInvitation(messageEvent->getStanza());
return;
}
else if (messageEvent->getStanza()->getPayload<MUCUserPayload>() && messageEvent->getStanza()->getPayload<MUCUserPayload>()->getInvite()) {
handleMediatedMUCInvitation(messageEvent->getStanza());
return;
}
else {
if (!messageEvent->isReadable()) {
return;
}
showChatWindow();
JID from = message->getFrom();
std::vector<boost::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>();
for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) {
if (!delayPayloads[i]->getFrom()) {
continue;
}
boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
std::ostringstream s;
s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 << " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << ".";
chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection);
}
boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>();
// Determine the timestamp
boost::posix_time::ptime timeStamp = boost::posix_time::microsec_clock::universal_time();
boost::optional<boost::posix_time::ptime> messageTimeStamp = getMessageTimestamp(message);
if (messageTimeStamp) {
timeStamp = *messageTimeStamp;
}
onActivity(body);
// Highlight
if (!isIncomingMessageFromMe(message)) {
highlight = highlighter_->findAction(body, senderDisplayNameFromMessage(from));
}
boost::shared_ptr<Replace> replace = message->getPayload<Replace>();
if (replace) {
std::string body = message->getBody();
// Should check if the user has a previous message
std::map<JID, std::string>::iterator lastMessage;
lastMessage = lastMessagesUIID_.find(from);
if (lastMessage != lastMessagesUIID_.end()) {
replaceMessage(body, lastMessagesUIID_[from], isIncomingMessageFromMe(message), timeStamp, highlight);
}
}
else {
lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, avatarManager_->getAvatarPath(from), timeStamp, highlight);
}
logMessage(body, from, selfJID_, timeStamp, true);
}
chatWindow_->show();
chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size()));
onUnreadCountChanged();
postHandleIncomingMessage(messageEvent, highlight);
}
std::string ChatControllerBase::getErrorMessage(boost::shared_ptr<ErrorPayload> error) {
std::string defaultMessage = QT_TRANSLATE_NOOP("", "Error sending message");
if (!error->getText().empty()) {
return error->getText();
}
else {
switch (error->getCondition()) {
case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request");
case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict");
case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented");
case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden");
case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted");
case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error");
case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found");
case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed");
case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected");
case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed");
case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized");
case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required");
case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable");
case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect");
case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required");
case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found");
case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout");
case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources");
case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable");
case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required");
case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition");
case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request");
}
}
assert(false);
return defaultMessage;
}
void ChatControllerBase::handleGeneralMUCInvitation(MUCInviteEvent::ref event) {
unreadMessages_.push_back(event);
chatWindow_->show();
chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size()));
onUnreadCountChanged();
chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect(), event->getImpromptu());
eventController_->handleIncomingEvent(event);
}
void ChatControllerBase::handleMUCInvitation(Message::ref message) {
MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>();
if (autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) {
eventStream_->send(boost::make_shared<JoinMUCUIEvent>(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true));
} else {
MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true, invite->getIsImpromptu());
handleGeneralMUCInvitation(inviteEvent);
}
}
void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) {
MUCUserPayload::Invite invite = *message->getPayload<MUCUserPayload>()->getInvite();
JID from = message->getFrom();
std::string reason;
if (!invite.reason.empty()) {
reason = invite.reason;
}
std::string password;
if (message->getPayload<MUCUserPayload>()->getPassword()) {
password = *message->getPayload<MUCUserPayload>()->getPassword();
}
MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false, false);
handleGeneralMUCInvitation(inviteEvent);
}
}
diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp
index 09d93ac..5a608db 100644
--- a/Swift/Controllers/Chat/ChatMessageParser.cpp
+++ b/Swift/Controllers/Chat/ChatMessageParser.cpp
@@ -1,194 +1,195 @@
/*
* Copyright (c) 2013-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <Swift/Controllers/Chat/ChatMessageParser.h>
#include <vector>
#include <utility>
#include <boost/smart_ptr/make_shared.hpp>
#include <boost/algorithm/string.hpp>
#include <Swiften/Base/Regex.h>
#include <Swiften/Base/foreach.h>
#include <SwifTools/Linkify.h>
namespace Swift {
ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode)
: emoticons_(emoticons), highlightRules_(highlightRules), mucMode_(mucMode) {
}
typedef std::pair<std::string, std::string> StringPair;
- ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, bool senderIsSelf) {
+ ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, const std::string& nick, bool senderIsSelf) {
ChatWindow::ChatMessage parsedMessage;
std::string remaining = body;
/* Parse one, URLs */
while (!remaining.empty()) {
bool found = false;
std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining);
remaining = "";
for (size_t i = 0; i < links.first.size(); i++) {
const std::string& part = links.first[i];
if (found) {
// Must be on the last part, then
remaining = part;
}
else {
if (i == links.second) {
found = true;
parsedMessage.append(boost::make_shared<ChatWindow::ChatURIMessagePart>(part));
}
else {
parsedMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(part));
}
}
}
}
/* do emoticon substitution */
parsedMessage = emoticonHighlight(parsedMessage);
if (!senderIsSelf) { /* do not highlight our own messsages */
/* do word-based color highlighting */
- parsedMessage = splitHighlight(parsedMessage);
+ parsedMessage = splitHighlight(parsedMessage, nick);
}
return parsedMessage;
}
ChatWindow::ChatMessage ChatMessageParser::emoticonHighlight(const ChatWindow::ChatMessage& message)
{
ChatWindow::ChatMessage parsedMessage = message;
std::string regexString;
/* Parse two, emoticons */
foreach (StringPair emoticon, emoticons_) {
/* Construct a regexp that finds an instance of any of the emoticons inside a group
* at the start or end of the line, or beside whitespace.
*/
regexString += regexString.empty() ? "" : "|";
std::string escaped = "(" + Regex::escape(emoticon.first) + ")";
regexString += "^" + escaped + "|";
regexString += escaped + "$|";
regexString += "\\s" + escaped + "|";
regexString += escaped + "\\s";
}
if (!regexString.empty()) {
regexString += "";
boost::regex emoticonRegex(regexString);
ChatWindow::ChatMessage newMessage;
foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) {
boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
try {
boost::match_results<std::string::const_iterator> match;
const std::string& text = textPart->text;
std::string::const_iterator start = text.begin();
while (regex_search(start, text.end(), match, emoticonRegex)) {
int matchIndex = 0;
for (matchIndex = 1; matchIndex < static_cast<int>(match.size()); matchIndex++) {
if (match[matchIndex].length() > 0) {
//This is the matching subgroup
break;
}
}
std::string::const_iterator matchStart = match[matchIndex].first;
std::string::const_iterator matchEnd = match[matchIndex].second;
if (start != matchStart) {
/* If we're skipping over plain text since the previous emoticon, record it as plain text */
newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart)));
}
boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = boost::make_shared<ChatWindow::ChatEmoticonMessagePart>();
std::string matchString = match[matchIndex].str();
std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(matchString);
assert (emoticonIterator != emoticons_.end());
const StringPair& emoticon = *emoticonIterator;
emoticonPart->imagePath = emoticon.second;
emoticonPart->alternativeText = emoticon.first;
newMessage.append(emoticonPart);
start = matchEnd;
}
if (start != text.end()) {
/* If there's plain text after the last emoticon, record it */
newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end())));
}
}
catch (std::runtime_error) {
/* Basically too expensive to compute the regex results and it gave up, so pass through as text */
newMessage.append(part);
}
}
else {
newMessage.append(part);
}
}
parsedMessage = newMessage;
}
return parsedMessage;
}
- ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message)
+ ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message, const std::string& nick)
{
ChatWindow::ChatMessage parsedMessage = message;
for (size_t i = 0; i < highlightRules_->getSize(); ++i) {
const HighlightRule& rule = highlightRules_->getRule(i);
if (rule.getMatchMUC() && !mucMode_) {
continue; /* this rule only applies to MUC's, and this is a CHAT */
} else if (rule.getMatchChat() && mucMode_) {
continue; /* this rule only applies to CHAT's, and this is a MUC */
}
- foreach(const boost::regex &regex, rule.getKeywordRegex()) {
+ const std::vector<boost::regex> keywordRegex = rule.getKeywordRegex(nick);
+ foreach(const boost::regex& regex, keywordRegex) {
ChatWindow::ChatMessage newMessage;
foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) {
boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
try {
boost::match_results<std::string::const_iterator> match;
const std::string& text = textPart->text;
std::string::const_iterator start = text.begin();
while (regex_search(start, text.end(), match, regex)) {
std::string::const_iterator matchStart = match[0].first;
std::string::const_iterator matchEnd = match[0].second;
if (start != matchStart) {
/* If we're skipping over plain text since the previous emoticon, record it as plain text */
newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart)));
}
boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = boost::make_shared<ChatWindow::ChatHighlightingMessagePart>();
highlightPart->text = match.str();
highlightPart->foregroundColor = rule.getAction().getTextColor();
highlightPart->backgroundColor = rule.getAction().getTextBackground();
newMessage.append(highlightPart);
start = matchEnd;
}
if (start != text.end()) {
/* If there's plain text after the last emoticon, record it */
newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end())));
}
}
catch (std::runtime_error) {
/* Basically too expensive to compute the regex results and it gave up, so pass through as text */
newMessage.append(part);
}
} else {
newMessage.append(part);
}
}
parsedMessage = newMessage;
}
}
return parsedMessage;
}
}
diff --git a/Swift/Controllers/Chat/ChatMessageParser.h b/Swift/Controllers/Chat/ChatMessageParser.h
index cff4ffa..2f5c171 100644
--- a/Swift/Controllers/Chat/ChatMessageParser.h
+++ b/Swift/Controllers/Chat/ChatMessageParser.h
@@ -1,26 +1,26 @@
/*
* Copyright (c) 2013-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
#include <string>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
namespace Swift {
class ChatMessageParser {
public:
ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode = false);
- ChatWindow::ChatMessage parseMessageBody(const std::string& body, bool senderIsSelf = false);
+ ChatWindow::ChatMessage parseMessageBody(const std::string& body, const std::string& nick = "", bool senderIsSelf = false);
private:
ChatWindow::ChatMessage emoticonHighlight(const ChatWindow::ChatMessage& parsedMessage);
- ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage);
+ ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& nick);
std::map<std::string, std::string> emoticons_;
HighlightRulesListPtr highlightRules_;
bool mucMode_;
};
}
diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
index 5dca63a..2a07654 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
@@ -1,231 +1,267 @@
/*
* Copyright (c) 2013-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <hippomocks.h>
#include <Swift/Controllers/Chat/ChatMessageParser.h>
using namespace Swift;
class ChatMessageParserTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(ChatMessageParserTest);
CPPUNIT_TEST(testFullBody);
CPPUNIT_TEST(testOneEmoticon);
CPPUNIT_TEST(testBareEmoticon);
CPPUNIT_TEST(testHiddenEmoticon);
CPPUNIT_TEST(testEndlineEmoticon);
CPPUNIT_TEST(testBoundedEmoticons);
CPPUNIT_TEST_SUITE_END();
public:
void setUp() {
smile1_ = ":)";
smile1Path_ = "/blah/smile1.png";
smile2_ = ":(";
smile2Path_ = "/blah/smile2.jpg";
emoticons_[smile1_] = smile1Path_;
emoticons_[smile2_] = smile2Path_;
}
void tearDown() {
emoticons_.clear();
}
void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
boost::shared_ptr<ChatWindow::ChatTextMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]);
CPPUNIT_ASSERT_EQUAL(text, part->text);
}
void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) {
boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]);
CPPUNIT_ASSERT(!!part);
CPPUNIT_ASSERT_EQUAL(text, part->alternativeText);
CPPUNIT_ASSERT_EQUAL(path, part->imagePath);
}
void assertHighlight(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(result.getParts()[index]);
CPPUNIT_ASSERT_EQUAL(text, part->text);
}
void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
boost::shared_ptr<ChatWindow::ChatURIMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]);
CPPUNIT_ASSERT_EQUAL(text, part->target);
}
static HighlightRule ruleFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord)
{
HighlightRule rule;
std::vector<std::string> keywords;
keywords.push_back(keyword);
rule.setKeywords(keywords);
rule.setMatchCase(matchCase);
rule.setMatchWholeWords(matchWholeWord);
rule.setMatchChat(true);
return rule;
}
static const HighlightRulesListPtr ruleListFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord)
{
boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>();
list->addRule(ruleFromKeyword(keyword, matchCase, matchWholeWord));
return list;
}
static const HighlightRulesListPtr ruleListFromKeywords(const HighlightRule &rule1, const HighlightRule &rule2)
{
boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>();
list->addRule(rule1);
list->addRule(rule2);
return list;
}
+ static HighlightRulesListPtr ruleListWithNickHighlight()
+ {
+ HighlightRule rule;
+ rule.setMatchChat(true);
+ rule.setNickIsKeyword(true);
+ rule.setMatchCase(true);
+ rule.setMatchWholeWords(true);
+ boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>();
+ list->addRule(rule);
+ return list;
+ }
+
void testFullBody() {
const std::string no_special_message = "a message with no special content";
ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody(no_special_message);
assertText(result, 0, no_special_message);
testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false));
result = testling.parseMessageBody(":) shiny :( trigger :) http://wonderland.lit/blah http://denmark.lit boom boom");
assertEmoticon(result, 0, smile1_, smile1Path_);
assertText(result, 1, " shiny ");
assertEmoticon(result, 2, smile2_, smile2Path_);
assertText(result, 3, " ");
assertHighlight(result, 4, "trigger");
assertText(result, 5, " ");
assertEmoticon(result, 6, smile1_, smile1Path_);
assertText(result, 7, " ");
assertURL(result, 8, "http://wonderland.lit/blah");
assertText(result, 9, " ");
assertURL(result, 10, "http://denmark.lit");
assertText(result, 11, " boom boom");
testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false));
result = testling.parseMessageBody("testtriggermessage");
assertText(result, 0, "test");
assertHighlight(result, 1, "trigger");
assertText(result, 2, "message");
testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, true));
result = testling.parseMessageBody("testtriggermessage");
assertText(result, 0, "testtriggermessage");
testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", true, false));
result = testling.parseMessageBody("TrIgGeR");
assertText(result, 0, "TrIgGeR");
testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false));
result = testling.parseMessageBody("TrIgGeR");
assertHighlight(result, 0, "TrIgGeR");
testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false));
result = testling.parseMessageBody("partialTrIgGeRmatch");
assertText(result, 0, "partial");
assertHighlight(result, 1, "TrIgGeR");
assertText(result, 2, "match");
testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false)));
result = testling.parseMessageBody("zero one two three");
assertText(result, 0, "zero ");
assertHighlight(result, 1, "one");
assertText(result, 2, " two ");
assertHighlight(result, 3, "three");
testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false)));
result = testling.parseMessageBody("zero oNe two tHrEe");
assertText(result, 0, "zero ");
assertHighlight(result, 1, "oNe");
assertText(result, 2, " two ");
assertHighlight(result, 3, "tHrEe");
testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", true, false)));
result = testling.parseMessageBody("zero oNe two tHrEe");
assertText(result, 0, "zero ");
assertHighlight(result, 1, "oNe");
assertText(result, 2, " two tHrEe");
testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", true, false)));
result = testling.parseMessageBody("zero oNe two tHrEe");
assertText(result, 0, "zero oNe two tHrEe");
testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false)));
result = testling.parseMessageBody("zeroonetwothree");
assertText(result, 0, "zero");
assertHighlight(result, 1, "one");
assertText(result, 2, "two");
assertHighlight(result, 3, "three");
testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", false, false)));
result = testling.parseMessageBody("zeroOnEtwoThReE");
assertText(result, 0, "zeroOnEtwo");
assertHighlight(result, 1, "ThReE");
testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, false)));
result = testling.parseMessageBody("zeroonetwothree");
assertText(result, 0, "zeroonetwo");
assertHighlight(result, 1, "three");
testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, true)));
result = testling.parseMessageBody("zeroonetwothree");
assertText(result, 0, "zeroonetwothree");
+
+ testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight());
+ result = testling.parseMessageBody("Alice", "Alice");
+ assertHighlight(result, 0, "Alice");
+
+ testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight());
+ result = testling.parseMessageBody("TextAliceText", "Alice");
+ assertText(result, 0, "TextAliceText");
+
+ testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight());
+ result = testling.parseMessageBody("Text Alice Text", "Alice");
+ assertText(result, 0, "Text ");
+ assertHighlight(result, 1, "Alice");
+ assertText(result, 2, " Text");
+
+ testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight());
+ result = testling.parseMessageBody("Alice Text", "Alice");
+ assertHighlight(result, 0, "Alice");
+ assertText(result, 1, " Text");
+
+ testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight());
+ result = testling.parseMessageBody("Text Alice", "Alice");
+ assertText(result, 0, "Text ");
+ assertHighlight(result, 1, "Alice");
}
void testOneEmoticon() {
ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody(" :) ");
assertText(result, 0, " ");
assertEmoticon(result, 1, smile1_, smile1Path_);
assertText(result, 2, " ");
}
void testBareEmoticon() {
ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody(":)");
assertEmoticon(result, 0, smile1_, smile1Path_);
}
void testHiddenEmoticon() {
ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a");
assertText(result, 0, "b:)a");
}
void testEndlineEmoticon() {
ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody("Lazy:)");
assertText(result, 0, "Lazy");
assertEmoticon(result, 1, smile1_, smile1Path_);
}
void testBoundedEmoticons() {
ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody(":)Lazy:(");
assertEmoticon(result, 0, smile1_, smile1Path_);
assertText(result, 1, "Lazy");
assertEmoticon(result, 2, smile2_, smile2Path_);
}
void testEmoticonParenthesis() {
ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>());
ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))");
assertText(result, 0, "(Like this ");
assertEmoticon(result, 1, smile1_, smile1Path_);
assertText(result, 2, ")");
}
private:
std::map<std::string, std::string> emoticons_;
std::string smile1_;
std::string smile1Path_;
std::string smile2_;
std::string smile2Path_;
};
CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageParserTest);
diff --git a/Swift/Controllers/HighlightAction.cpp b/Swift/Controllers/HighlightAction.cpp
index d4d199d..492d4d2 100644
--- a/Swift/Controllers/HighlightAction.cpp
+++ b/Swift/Controllers/HighlightAction.cpp
@@ -1,28 +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)
+void HighlightAction::setHighlightAllText(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
index de1f201..90768a7 100644
--- a/Swift/Controllers/HighlightAction.h
+++ b/Swift/Controllers/HighlightAction.h
@@ -1,76 +1,79 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
/*
* Copyright (c) 2014 Kevin Smith and Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
#include <string>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
namespace Swift {
class HighlightRule;
class HighlightAction {
public:
HighlightAction() : highlightText_(false), playSound_(false) {}
- bool highlightText() const { return highlightText_; }
- void setHighlightText(bool highlightText);
+ /**
+ * Gets the flag that indicates the entire message should be highlighted.
+ */
+ bool highlightAllText() const { return highlightText_; }
+ void setHighlightAllText(bool highlightText);
/**
- * Gets the foreground highlight color. If the string is empty, assume a default color.
+ * Gets the foreground highlight color.
*/
const std::string& getTextColor() const { return textColor_; }
void setTextColor(const std::string& textColor) { textColor_ = textColor; }
/**
- * Gets the background highlight color. If the string is empty, assume a default color.
+ * Gets the background highlight color.
*/
const std::string& getTextBackground() const { return textBackground_; }
void setTextBackground(const std::string& textBackground) { textBackground_ = textBackground; }
bool playSound() const { return playSound_; }
void setPlaySound(bool playSound);
/**
* Gets the sound filename. If the string is empty, assume a default sound file.
*/
const std::string& getSoundFile() const { return soundFile_; }
void setSoundFile(const std::string& soundFile) { soundFile_ = soundFile; }
bool isEmpty() const { return !highlightText_ && !playSound_; }
private:
friend class boost::serialization::access;
template<class Archive> void serialize(Archive & ar, const unsigned int version);
bool highlightText_;
std::string textColor_;
std::string textBackground_;
bool playSound_;
std::string soundFile_;
};
template<class Archive>
void HighlightAction::serialize(Archive& ar, const unsigned int /*version*/)
{
ar & highlightText_;
ar & textColor_;
ar & textBackground_;
ar & playSound_;
ar & soundFile_;
}
}
diff --git a/Swift/Controllers/HighlightRule.cpp b/Swift/Controllers/HighlightRule.cpp
index f1a5235..3251067 100644
--- a/Swift/Controllers/HighlightRule.cpp
+++ b/Swift/Controllers/HighlightRule.cpp
@@ -1,140 +1,152 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
/*
* Copyright (c) 2014 Kevin Smith and Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <boost/lambda/lambda.hpp>
#include <Swiften/Base/foreach.h>
#include <Swiften/Base/Regex.h>
#include <Swift/Controllers/HighlightRule.h>
namespace Swift {
HighlightRule::HighlightRule()
: nickIsKeyword_(false)
, matchCase_(false)
, matchWholeWords_(false)
, matchChat_(false)
, matchMUC_(false)
{
}
boost::regex HighlightRule::regexFromString(const std::string & s) const
{
std::string escaped = Regex::escape(s);
std::string word = matchWholeWords_ ? "\\b" : "";
boost::regex::flag_type flags = boost::regex::normal;
if (!matchCase_) {
flags |= boost::regex::icase;
}
return boost::regex(word + escaped + word, flags);
}
void HighlightRule::updateRegex() const
{
keywordRegex_.clear();
foreach (const std::string & k, keywords_) {
keywordRegex_.push_back(regexFromString(k));
}
senderRegex_.clear();
foreach (const std::string & s, senders_) {
senderRegex_.push_back(regexFromString(s));
}
}
std::string HighlightRule::boolToString(bool b)
{
return b ? "1" : "0";
}
bool HighlightRule::boolFromString(const std::string& s)
{
return s == "1";
}
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();
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();
}
+std::vector<boost::regex> HighlightRule::getKeywordRegex(const std::string& nick) const {
+ if (nickIsKeyword_) {
+ std::vector<boost::regex> regex;
+ if (!nick.empty()) {
+ regex.push_back(regexFromString(nick));
+ }
+ return regex;
+ } else {
+ return keywordRegex_;
+ }
+}
+
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
index ae1a3d3..c0226bc 100644
--- a/Swift/Controllers/HighlightRule.h
+++ b/Swift/Controllers/HighlightRule.h
@@ -1,101 +1,101 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
/*
* Copyright (c) 2014 Kevin Smith and Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
#include <vector>
#include <string>
#include <boost/regex.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <Swift/Controllers/HighlightAction.h>
namespace Swift {
class HighlightRule {
public:
HighlightRule();
enum MessageType { ChatMessage, MUCMessage };
bool isMatch(const std::string& body, const std::string& sender, const std::string& nick, MessageType) const;
const HighlightAction& getAction() const { return action_; }
HighlightAction& getAction() { return action_; }
const std::vector<std::string>& getSenders() const { return senders_; }
void setSenders(const std::vector<std::string>&);
const std::vector<boost::regex>& getSenderRegex() const { return senderRegex_; }
const std::vector<std::string>& getKeywords() const { return keywords_; }
void setKeywords(const std::vector<std::string>&);
- const std::vector<boost::regex>& getKeywordRegex() const { return keywordRegex_; }
+ std::vector<boost::regex> getKeywordRegex(const std::string& nick) const;
bool getNickIsKeyword() const { return nickIsKeyword_; }
void setNickIsKeyword(bool);
bool getMatchCase() const { return matchCase_; }
void setMatchCase(bool);
bool getMatchWholeWords() const { return matchWholeWords_; }
void setMatchWholeWords(bool);
bool getMatchChat() const { return matchChat_; }
void setMatchChat(bool);
bool getMatchMUC() const { return matchMUC_; }
void setMatchMUC(bool);
bool isEmpty() const;
private:
friend class boost::serialization::access;
template<class Archive> void serialize(Archive & ar, const unsigned int version);
static std::string boolToString(bool);
static bool boolFromString(const std::string&);
std::vector<std::string> senders_;
std::vector<std::string> keywords_;
bool nickIsKeyword_;
mutable std::vector<boost::regex> senderRegex_;
mutable std::vector<boost::regex> keywordRegex_;
void updateRegex() const;
boost::regex regexFromString(const std::string&) const;
bool matchCase_;
bool matchWholeWords_;
bool matchChat_;
bool matchMUC_;
HighlightAction action_;
};
template<class Archive>
void HighlightRule::serialize(Archive& ar, const unsigned int /*version*/)
{
ar & senders_;
ar & keywords_;
ar & nickIsKeyword_;
ar & matchChat_;
ar & matchMUC_;
ar & matchCase_;
ar & matchWholeWords_;
ar & action_;
updateRegex();
}
}
diff --git a/Swift/Controllers/Highlighter.h b/Swift/Controllers/Highlighter.h
index d026f29..d5d846b 100644
--- a/Swift/Controllers/Highlighter.h
+++ b/Swift/Controllers/Highlighter.h
@@ -1,37 +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 <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; }
+ std::string getNick() const { return 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/QtUI/QtHighlightEditor.cpp b/Swift/QtUI/QtHighlightEditor.cpp
index ce07003..8488d7d 100644
--- a/Swift/QtUI/QtHighlightEditor.cpp
+++ b/Swift/QtUI/QtHighlightEditor.cpp
@@ -1,524 +1,513 @@
/*
* Copyright (c) 2012 Maciej Niedzielski
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
/*
* Copyright (c) 2014 Kevin Smith and Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#include <cassert>
#include <boost/lexical_cast.hpp>
#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h>
#include <Swift/Controllers/HighlightManager.cpp>
#include <Swift/QtUI/QtHighlightEditor.h>
#include <Swift/QtUI/QtSwiftUtil.h>
#include <Swift/QtUI/QtSettingsProvider.h>
#include <QTreeWidgetItem>
#include <QFileDialog>
namespace Swift {
QtHighlightEditor::QtHighlightEditor(QtSettingsProvider* settings, QWidget* parent)
: QWidget(parent), settings_(settings), previousRow_(-1)
{
ui_.setupUi(this);
connect(ui_.listWidget, SIGNAL(currentRowChanged(int)), SLOT(onCurrentRowChanged(int)));
connect(ui_.newButton, SIGNAL(clicked()), SLOT(onNewButtonClicked()));
connect(ui_.deleteButton, SIGNAL(clicked()), SLOT(onDeleteButtonClicked()));
connect(ui_.buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(onApplyButtonClick()));
connect(ui_.buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), SLOT(onCancelButtonClick()));
connect(ui_.buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(onOkButtonClick()));
connect(ui_.noColorRadio, SIGNAL(clicked()), SLOT(colorOtherSelect()));
- connect(ui_.defaultColorRadio, SIGNAL(clicked()), SLOT(colorOtherSelect()));
connect(ui_.customColorRadio, SIGNAL(clicked()), SLOT(colorCustomSelect()));
connect(ui_.noSoundRadio, SIGNAL(clicked()), SLOT(soundOtherSelect()));
connect(ui_.defaultSoundRadio, SIGNAL(clicked()), SLOT(soundOtherSelect()));
connect(ui_.customSoundRadio, SIGNAL(clicked()), SLOT(soundCustomSelect()));
/* replace the static line-edit control with the roster autocompleter */
ui_.dummySenderName->setVisible(false);
jid_ = new QtSuggestingJIDInput(this, settings);
ui_.senderName->addWidget(jid_);
jid_->onUserSelected.connect(boost::bind(&QtHighlightEditor::handleOnUserSelected, this, _1));
/* handle autocomplete */
connect(jid_, SIGNAL(textEdited(QString)), SLOT(handleContactSuggestionRequested(QString)));
/* we need to be notified if any of the state changes so that we can update our textual rule description */
connect(ui_.chatRadio, SIGNAL(clicked()), SLOT(widgetClick()));
connect(ui_.roomRadio, SIGNAL(clicked()), SLOT(widgetClick()));
connect(ui_.nickIsKeyword, SIGNAL(clicked()), SLOT(widgetClick()));
connect(ui_.allMsgRadio, SIGNAL(clicked()), SLOT(widgetClick()));
connect(ui_.senderRadio, SIGNAL(clicked()), SLOT(widgetClick()));
connect(jid_, SIGNAL(textChanged(const QString&)), SLOT(widgetClick()));
connect(ui_.keywordRadio, SIGNAL(clicked()), SLOT(widgetClick()));
connect(ui_.keyword, SIGNAL(textChanged(const QString&)), SLOT(widgetClick()));
connect(ui_.matchPartialWords, SIGNAL(clicked()), SLOT(widgetClick()));
connect(ui_.matchCase, SIGNAL(clicked()), SLOT(widgetClick()));
connect(ui_.noColorRadio, SIGNAL(clicked()), SLOT(widgetClick()));
- connect(ui_.defaultColorRadio, SIGNAL(clicked()), SLOT(widgetClick()));
connect(ui_.customColorRadio, SIGNAL(clicked()), SLOT(widgetClick()));
connect(ui_.noSoundRadio, SIGNAL(clicked()), SLOT(widgetClick()));
connect(ui_.defaultSoundRadio, SIGNAL(clicked()), SLOT(widgetClick()));
connect(ui_.customSoundRadio, SIGNAL(clicked()), SLOT(widgetClick()));
/* allow selection of a custom sound file */
connect(ui_.soundFileButton, SIGNAL(clicked()), SLOT(selectSoundFile()));
/* if these are not needed, then they should be removed */
ui_.moveUpButton->setVisible(false);
ui_.moveDownButton->setVisible(false);
setWindowTitle(tr("Highlight Rules"));
}
QtHighlightEditor::~QtHighlightEditor()
{
}
std::string formatShortDescription(const HighlightRule &rule)
{
const std::string chatOrRoom = (rule.getMatchChat() ? "chat" : "room");
std::vector<std::string> senders = rule.getSenders();
std::vector<std::string> keywords = rule.getKeywords();
if (senders.empty() && keywords.empty() && !rule.getNickIsKeyword()) {
return std::string("All ") + chatOrRoom + " messages.";
}
if (rule.getNickIsKeyword()) {
return std::string("All ") + chatOrRoom + " messages that mention my name.";
}
if (!senders.empty()) {
return std::string("All ") + chatOrRoom + " messages from " + senders[0] + ".";
}
if (!keywords.empty()) {
return std::string("All ") + chatOrRoom + " messages mentioning the keyword '" + keywords[0] + "'.";
}
return "Unknown Rule";
}
void QtHighlightEditor::show()
{
highlightManager_->loadSettings();
populateList();
if (ui_.listWidget->count()) {
selectRow(0);
}
/* prepare default states */
widgetClick();
QWidget::show();
QWidget::activateWindow();
}
void QtHighlightEditor::setHighlightManager(HighlightManager* highlightManager)
{
highlightManager_ = highlightManager;
}
void QtHighlightEditor::setContactSuggestions(const std::vector<Contact::ref>& suggestions)
{
jid_->setSuggestions(suggestions);
}
void QtHighlightEditor::colorOtherSelect()
{
ui_.foregroundColor->setEnabled(false);
ui_.backgroundColor->setEnabled(false);
}
void QtHighlightEditor::colorCustomSelect()
{
ui_.foregroundColor->setEnabled(true);
ui_.backgroundColor->setEnabled(true);
}
void QtHighlightEditor::soundOtherSelect()
{
ui_.soundFile->setEnabled(false);
ui_.soundFileButton->setEnabled(false);
}
void QtHighlightEditor::soundCustomSelect()
{
ui_.soundFile->setEnabled(true);
ui_.soundFileButton->setEnabled(true);
}
void QtHighlightEditor::onNewButtonClicked()
{
int row = getSelectedRow() + 1;
populateList();
HighlightRule newRule;
newRule.setMatchMUC(true);
highlightManager_->insertRule(row, newRule);
QListWidgetItem *item = new QListWidgetItem();
item->setText(P2QSTRING(formatShortDescription(newRule)));
ui_.listWidget->insertItem(row, item);
selectRow(row);
}
void QtHighlightEditor::onDeleteButtonClicked()
{
int selectedRow = getSelectedRow();
assert(selectedRow>=0 && selectedRow<ui_.listWidget->count());
delete ui_.listWidget->takeItem(selectedRow);
highlightManager_->removeRule(selectedRow);
if (!ui_.listWidget->count()) {
disableDialog();
ui_.deleteButton->setEnabled(false);
} else {
if (selectedRow == ui_.listWidget->count()) {
selectRow(ui_.listWidget->count() - 1);
} else {
selectRow(selectedRow);
}
}
}
void QtHighlightEditor::onCurrentRowChanged(int currentRow)
{
ui_.deleteButton->setEnabled(currentRow != -1);
ui_.moveUpButton->setEnabled(currentRow != -1 && currentRow != 0);
ui_.moveDownButton->setEnabled(currentRow != -1 && currentRow != (ui_.listWidget->count()-1));
if (previousRow_ != -1) {
if (ui_.listWidget->count() > previousRow_) {
highlightManager_->setRule(previousRow_, ruleFromDialog());
}
}
if (currentRow != -1) {
HighlightRule rule = highlightManager_->getRule(currentRow);
ruleToDialog(rule);
if (ui_.listWidget->currentItem()) {
ui_.listWidget->currentItem()->setText(P2QSTRING(formatShortDescription(rule)));
}
}
/* grey the dialog if we have nothing selected */
if (currentRow == -1) {
disableDialog();
}
previousRow_ = currentRow;
}
void QtHighlightEditor::onApplyButtonClick()
{
selectRow(getSelectedRow()); /* force save */
highlightManager_->storeSettings();
}
void QtHighlightEditor::onCancelButtonClick()
{
close();
}
void QtHighlightEditor::onOkButtonClick()
{
onApplyButtonClick();
close();
}
void QtHighlightEditor::setChildWidgetStates()
{
/* disable appropriate radio button child widgets */
if (ui_.chatRadio->isChecked()) {
if (ui_.nickIsKeyword->isChecked()) {
/* switch to another choice before we disable this button */
ui_.allMsgRadio->setChecked(true);
}
ui_.nickIsKeyword->setEnabled(false);
} else if (ui_.roomRadio->isChecked()) {
ui_.nickIsKeyword->setEnabled(true);
} else { /* chats and rooms */
ui_.nickIsKeyword->setEnabled(true);
}
if (ui_.senderRadio->isChecked()) {
jid_->setEnabled(true);
} else {
jid_->setEnabled(false);
}
if (ui_.keywordRadio->isChecked()) {
ui_.keyword->setEnabled(true);
ui_.matchPartialWords->setEnabled(true);
ui_.matchCase->setEnabled(true);
} else {
ui_.keyword->setEnabled(false);
ui_.matchPartialWords->setEnabled(false);
ui_.matchCase->setEnabled(false);
}
if (ui_.chatRadio->isChecked()) {
ui_.allMsgRadio->setText(tr("Apply to all chat messages"));
} else {
ui_.allMsgRadio->setText(tr("Apply to all room messages"));
}
}
void QtHighlightEditor::widgetClick()
{
setChildWidgetStates();
HighlightRule rule = ruleFromDialog();
if (ui_.listWidget->currentItem()) {
ui_.listWidget->currentItem()->setText(P2QSTRING(formatShortDescription(rule)));
}
}
void QtHighlightEditor::disableDialog()
{
ui_.chatRadio->setEnabled(false);
ui_.roomRadio->setEnabled(false);
ui_.allMsgRadio->setEnabled(false);
ui_.nickIsKeyword->setEnabled(false);
ui_.senderRadio->setEnabled(false);
ui_.dummySenderName->setEnabled(false);
ui_.keywordRadio->setEnabled(false);
ui_.keyword->setEnabled(false);
ui_.matchPartialWords->setEnabled(false);
ui_.matchCase->setEnabled(false);
ui_.noColorRadio->setEnabled(false);
- ui_.defaultColorRadio->setEnabled(false);
ui_.customColorRadio->setEnabled(false);
ui_.foregroundColor->setEnabled(false);
ui_.backgroundColor->setEnabled(false);
ui_.noSoundRadio->setEnabled(false);
ui_.defaultSoundRadio->setEnabled(false);
ui_.customSoundRadio->setEnabled(false);
ui_.soundFile->setEnabled(false);
ui_.soundFileButton->setEnabled(false);
}
void QtHighlightEditor::handleContactSuggestionRequested(const QString& text)
{
std::string stdText = Q2PSTRING(text);
onContactSuggestionsRequested(stdText);
}
void QtHighlightEditor::selectSoundFile()
{
QString path = QFileDialog::getOpenFileName(this, tr("Select sound file..."), QString(), "Sounds (*.wav)");
ui_.soundFile->setText(path);
}
void QtHighlightEditor::handleOnUserSelected(const JID& jid) {
/* this might seem like it should be standard behaviour for the suggesting input box, but is not desirable in all cases */
jid_->setText(P2QSTRING(jid.toString()));
}
void QtHighlightEditor::populateList()
{
previousRow_ = -1;
ui_.listWidget->clear();
HighlightRulesListPtr rules = highlightManager_->getRules();
for (size_t i = 0; i < rules->getSize(); ++i) {
const HighlightRule& rule = rules->getRule(i);
QListWidgetItem *item = new QListWidgetItem();
item->setText(P2QSTRING(formatShortDescription(rule)));
ui_.listWidget->addItem(item);
}
}
void QtHighlightEditor::selectRow(int row)
{
for (int i = 0; i < ui_.listWidget->count(); ++i) {
if (i == row) {
ui_.listWidget->item(i)->setSelected(true);
onCurrentRowChanged(i);
} else {
ui_.listWidget->item(i)->setSelected(false);
}
}
ui_.listWidget->setCurrentRow(row);
}
int QtHighlightEditor::getSelectedRow() const
{
for (int i = 0; i < ui_.listWidget->count(); ++i) {
if (ui_.listWidget->item(i)->isSelected()) {
return i;
}
}
return -1;
}
HighlightRule QtHighlightEditor::ruleFromDialog()
{
HighlightRule rule;
if (ui_.chatRadio->isChecked()) {
rule.setMatchChat(true);
rule.setMatchMUC(false);
} else {
rule.setMatchChat(false);
rule.setMatchMUC(true);
}
if (ui_.senderRadio->isChecked()) {
QString senderName = jid_->text();
if (!senderName.isEmpty()) {
std::vector<std::string> senders;
senders.push_back(Q2PSTRING(senderName));
rule.setSenders(senders);
}
}
if (ui_.keywordRadio->isChecked()) {
QString keywordString = ui_.keyword->text();
if (!keywordString.isEmpty()) {
std::vector<std::string> keywords;
keywords.push_back(Q2PSTRING(keywordString));
rule.setKeywords(keywords);
}
}
- rule.setNickIsKeyword(ui_.nickIsKeyword->isChecked());
- rule.setMatchWholeWords(!ui_.matchPartialWords->isChecked());
- rule.setMatchCase(ui_.matchCase->isChecked());
+ if (ui_.nickIsKeyword->isChecked()) {
+ rule.setNickIsKeyword(true);
+ rule.setMatchWholeWords(true);
+ rule.setMatchCase(true);
+ } else {
+ rule.setMatchWholeWords(!ui_.matchPartialWords->isChecked());
+ rule.setMatchCase(ui_.matchCase->isChecked());
+ }
HighlightAction& action = rule.getAction();
if (ui_.noColorRadio->isChecked()) {
- action.setHighlightText(false);
- action.setTextColor("");
- action.setTextBackground("");
- } else if (ui_.defaultColorRadio->isChecked()) {
- action.setHighlightText(true);
action.setTextColor("");
action.setTextBackground("");
} else {
- action.setHighlightText(true);
action.setTextColor(Q2PSTRING(ui_.foregroundColor->getColor().name()));
action.setTextBackground(Q2PSTRING(ui_.backgroundColor->getColor().name()));
}
if (ui_.noSoundRadio->isChecked()) {
action.setPlaySound(false);
} else if (ui_.defaultSoundRadio->isChecked()) {
action.setPlaySound(true);
action.setSoundFile("");
} else {
action.setPlaySound(true);
action.setSoundFile(Q2PSTRING(ui_.soundFile->text()));
}
return rule;
}
void QtHighlightEditor::ruleToDialog(const HighlightRule& rule)
{
ui_.chatRadio->setEnabled(true);
ui_.roomRadio->setEnabled(true);
if (rule.getMatchMUC()) {
ui_.chatRadio->setChecked(false);
ui_.roomRadio->setChecked(true);
} else {
ui_.chatRadio->setChecked(true);
ui_.roomRadio->setChecked(false);
}
ui_.allMsgRadio->setEnabled(true);
ui_.allMsgRadio->setChecked(true); /* this is the default radio button */
jid_->setText("");
ui_.keyword->setText("");
ui_.matchPartialWords->setChecked(false);
ui_.matchCase->setChecked(false);
ui_.nickIsKeyword->setEnabled(true);
if (rule.getNickIsKeyword()) {
ui_.nickIsKeyword->setChecked(true);
}
ui_.senderRadio->setEnabled(true);
std::vector<std::string> senders = rule.getSenders();
if (!senders.empty()) {
ui_.senderRadio->setChecked(true);
jid_->setText(P2QSTRING(senders[0]));
}
ui_.keywordRadio->setEnabled(true);
std::vector<std::string> keywords = rule.getKeywords();
if (!keywords.empty()) {
ui_.keywordRadio->setChecked(true);
ui_.keyword->setText(P2QSTRING(keywords[0]));
ui_.matchPartialWords->setChecked(!rule.getMatchWholeWords());
ui_.matchCase->setChecked(rule.getMatchCase());
}
const HighlightAction& action = rule.getAction();
ui_.noColorRadio->setEnabled(true);
- ui_.defaultColorRadio->setEnabled(true);
ui_.customColorRadio->setEnabled(true);
- if (action.highlightText()) {
- if (action.getTextColor().empty() && action.getTextBackground().empty()) {
- ui_.defaultColorRadio->setChecked(true);
- ui_.foregroundColor->setEnabled(false);
- ui_.backgroundColor->setEnabled(false);
- } else {
- ui_.foregroundColor->setEnabled(true);
- ui_.backgroundColor->setEnabled(true);
- QColor foregroundColor(P2QSTRING(action.getTextColor()));
- ui_.foregroundColor->setColor(foregroundColor);
- QColor backgroundColor(P2QSTRING(action.getTextBackground()));
- ui_.backgroundColor->setColor(backgroundColor);
- ui_.customColorRadio->setChecked(true);
- }
- } else {
+ if (action.getTextColor().empty() && action.getTextBackground().empty()) {
ui_.noColorRadio->setChecked(true);
ui_.foregroundColor->setEnabled(false);
ui_.backgroundColor->setEnabled(false);
+ } else {
+ ui_.foregroundColor->setEnabled(true);
+ ui_.backgroundColor->setEnabled(true);
+ QColor foregroundColor(P2QSTRING(action.getTextColor()));
+ ui_.foregroundColor->setColor(foregroundColor);
+ QColor backgroundColor(P2QSTRING(action.getTextBackground()));
+ ui_.backgroundColor->setColor(backgroundColor);
+ ui_.customColorRadio->setChecked(true);
}
ui_.noSoundRadio->setEnabled(true);
ui_.defaultSoundRadio->setEnabled(true);
ui_.customSoundRadio->setEnabled(true);
ui_.soundFile->setText("");
ui_.soundFile->setEnabled(false);
ui_.soundFileButton->setEnabled(false);
if (action.playSound()) {
if (action.getSoundFile().empty()) {
ui_.defaultSoundRadio->setChecked(true);
} else {
ui_.customSoundRadio->setChecked(true);
ui_.soundFile->setText(P2QSTRING(action.getSoundFile()));
ui_.soundFile->setEnabled(true);
ui_.soundFileButton->setEnabled(true);
}
} else {
ui_.noSoundRadio->setChecked(true);
}
/* set radio button child option states */
setChildWidgetStates();
}
}
diff --git a/Swift/QtUI/QtHighlightEditor.ui b/Swift/QtUI/QtHighlightEditor.ui
index be2e99b..775771f 100644
--- a/Swift/QtUI/QtHighlightEditor.ui
+++ b/Swift/QtUI/QtHighlightEditor.ui
@@ -1,486 +1,479 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QtHighlightEditor</class>
<widget class="QWidget" name="QtHighlightEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>600</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>600</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>463</width>
<height>792</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Incoming messages are checked against the following rules. First rule that matches will be executed.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="listWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="newButton">
<property name="text">
<string>New Rule</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteButton">
<property name="text">
<string>Remove Rule</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="moveUpButton">
<property name="text">
<string>Move Up</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="moveDownButton">
<property name="text">
<string>Move Down</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Apply Rule To</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="roomRadio">
<property name="text">
<string>Rooms</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="chatRadio">
<property name="text">
<string>Chats</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>246</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Rule Conditions</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="allMsgRadio">
<property name="text">
<string>Apply to all messages</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="nickIsKeyword">
<property name="text">
<string>Only messages mentioning me</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="senderRadio">
<property name="text">
<string>Messages from this sender:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="senderName">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<widget class="QLineEdit" name="dummySenderName"/>
</item>
</layout>
</item>
<item>
<widget class="QRadioButton" name="keywordRadio">
<property name="text">
<string>Messages containing this keyword:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="keyword"/>
</item>
<item>
<widget class="QCheckBox" name="matchPartialWords">
<property name="text">
<string>Match keyword within longer words</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="matchCase">
<property name="text">
<string>Keyword is case sensitive</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Highlight Action</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QRadioButton" name="noColorRadio">
<property name="text">
<string>No Highlight</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
- <widget class="QRadioButton" name="defaultColorRadio">
- <property name="text">
- <string>Default Color</string>
- </property>
- </widget>
- </item>
- <item>
<widget class="QRadioButton" name="customColorRadio">
<property name="text">
<string>Custom Color</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Swift::QtColorToolButton" name="foregroundColor">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Text</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="Swift::QtColorToolButton" name="backgroundColor">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Background</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Sound Action</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QRadioButton" name="noSoundRadio">
<property name="text">
<string>No Sound</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="defaultSoundRadio">
<property name="text">
<string>Default Sound</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="customSoundRadio">
<property name="text">
<string>Custom Sound</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLineEdit" name="soundFile">
<property name="enabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="soundFileButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Swift::QtColorToolButton</class>
<extends>QToolButton</extends>
<header>QtColorToolButton.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/Swift/QtUI/QtWebKitChatView.cpp b/Swift/QtUI/QtWebKitChatView.cpp
index 1486293..a510e34 100644
--- a/Swift/QtUI/QtWebKitChatView.cpp
+++ b/Swift/QtUI/QtWebKitChatView.cpp
@@ -68,878 +68,878 @@ QtWebKitChatView::QtWebKitChatView(QtChatWindow* window, UIEventStream* eventStr
stack->setLineWidth(2);
mainLayout->addWidget(stack);
#else
mainLayout->addWidget(webView_);
#endif
#ifdef SWIFT_EXPERIMENTAL_FT
setAcceptDrops(true);
#endif
webPage_ = new QWebPage(this);
webPage_->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
if (Log::getLogLevel() == Log::debug) {
webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
}
webView_->setPage(webPage_);
connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard()));
connect(webPage_, SIGNAL(scrollRequested(int, int, const QRect&)), SLOT(handleScrollRequested(int, int, const QRect&)));
viewReady_ = false;
isAtBottom_ = true;
resetView();
jsBridge = new QtChatWindowJSBridge();
addToJSEnvironment("chatwindow", jsBridge);
connect(jsBridge, SIGNAL(buttonClicked(QString,QString,QString,QString,QString,QString)), this, SLOT(handleHTMLButtonClicked(QString,QString,QString,QString,QString,QString)));
}
QtWebKitChatView::~QtWebKitChatView() {
delete jsBridge;
}
void QtWebKitChatView::handleClearRequested() {
QMessageBox messageBox(this);
messageBox.setWindowTitle(tr("Clear log"));
messageBox.setText(tr("You are about to clear the contents of your chat log."));
messageBox.setInformativeText(tr("Are you sure?"));
messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
messageBox.setDefaultButton(QMessageBox::Yes);
int button = messageBox.exec();
if (button == QMessageBox::Yes) {
logCleared();
resetView();
}
}
void QtWebKitChatView::handleKeyPressEvent(QKeyEvent* event) {
webView_->keyPressEvent(event);
}
void QtWebKitChatView::addMessageBottom(boost::shared_ptr<ChatSnippet> snippet) {
if (viewReady_) {
addToDOM(snippet);
} else {
/* If this asserts, the previous queuing code was necessary and should be reinstated */
assert(false);
}
}
void QtWebKitChatView::addMessageTop(boost::shared_ptr<ChatSnippet> snippet) {
// save scrollbar maximum value
if (!topMessageAdded_) {
scrollBarMaximum_ = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical);
}
topMessageAdded_ = true;
QWebElement continuationElement = firstElement_.findFirst("#insert");
bool insert = snippet->getAppendToPrevious();
bool fallback = continuationElement.isNull();
boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet;
QWebElement newElement = snippetToDOM(newSnippet);
if (insert && !fallback) {
Q_ASSERT(!continuationElement.isNull());
continuationElement.replace(newElement);
} else {
continuationElement.removeFromDocument();
topInsertPoint_.prependOutside(newElement);
}
firstElement_ = newElement;
if (lastElement_.isNull()) {
lastElement_ = firstElement_;
}
if (fontSizeSteps_ != 0) {
double size = 1.0 + 0.2 * fontSizeSteps_;
QString sizeString(QString().setNum(size, 'g', 3) + "em");
const QWebElementCollection spans = firstElement_.findAll("span.swift_resizable");
Q_FOREACH (QWebElement span, spans) {
span.setStyleProperty("font-size", sizeString);
}
}
}
QWebElement QtWebKitChatView::snippetToDOM(boost::shared_ptr<ChatSnippet> snippet) {
QWebElement newElement = newInsertPoint_.clone();
newElement.setInnerXml(snippet->getContent());
Q_ASSERT(!newElement.isNull());
return newElement;
}
void QtWebKitChatView::addToDOM(boost::shared_ptr<ChatSnippet> snippet) {
//qDebug() << snippet->getContent();
rememberScrolledToBottom();
bool insert = snippet->getAppendToPrevious();
QWebElement continuationElement = lastElement_.findFirst("#insert");
bool fallback = insert && continuationElement.isNull();
boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet;
QWebElement newElement = snippetToDOM(newSnippet);
if (insert && !fallback) {
Q_ASSERT(!continuationElement.isNull());
continuationElement.replace(newElement);
} else {
continuationElement.removeFromDocument();
newInsertPoint_.prependOutside(newElement);
}
lastElement_ = newElement;
if (fontSizeSteps_ != 0) {
double size = 1.0 + 0.2 * fontSizeSteps_;
QString sizeString(QString().setNum(size, 'g', 3) + "em");
const QWebElementCollection spans = lastElement_.findAll("span.swift_resizable");
Q_FOREACH (QWebElement span, spans) {
span.setStyleProperty("font-size", sizeString);
}
}
//qDebug() << "-----------------";
//qDebug() << webPage_->mainFrame()->toHtml();
}
void QtWebKitChatView::addLastSeenLine() {
/* if the line is added we should break the snippet */
insertingLastLine_ = true;
if (lineSeparator_.isNull()) {
lineSeparator_ = newInsertPoint_.clone();
lineSeparator_.setInnerXml(QString("<hr/>"));
newInsertPoint_.prependOutside(lineSeparator_);
}
else {
QWebElement lineSeparatorC = lineSeparator_.clone();
lineSeparatorC.removeFromDocument();
}
newInsertPoint_.prependOutside(lineSeparator_);
}
void QtWebKitChatView::replaceLastMessage(const QString& newMessage, const ChatWindow::TimestampBehaviour timestampBehaviour) {
assert(viewReady_);
rememberScrolledToBottom();
assert(!lastElement_.isNull());
QWebElement replace = lastElement_.findFirst("span.swift_message");
assert(!replace.isNull());
QString old = lastElement_.toOuterXml();
replace.setInnerXml(ChatSnippet::escape(newMessage));
if (timestampBehaviour == ChatWindow::UpdateTimestamp) {
replace = lastElement_.findFirst("span.swift_time");
assert(!replace.isNull());
replace.setInnerXml(ChatSnippet::timeToEscapedString(QDateTime::currentDateTime()));
}
}
void QtWebKitChatView::replaceLastMessage(const QString& newMessage, const QString& note) {
rememberScrolledToBottom();
replaceLastMessage(newMessage, ChatWindow::KeepTimestamp);
QWebElement replace = lastElement_.findFirst("span.swift_time");
assert(!replace.isNull());
replace.setInnerXml(ChatSnippet::escape(note));
}
QString QtWebKitChatView::getLastSentMessage() {
return lastElement_.toPlainText();
}
void QtWebKitChatView::addToJSEnvironment(const QString& name, QObject* obj) {
webView_->page()->currentFrame()->addToJavaScriptWindowObject(name, obj);
}
void QtWebKitChatView::replaceMessage(const QString& newMessage, const QString& id, const QDateTime& editTime) {
rememberScrolledToBottom();
QWebElement message = document_.findFirst("#" + id);
if (!message.isNull()) {
QWebElement replaceContent = message.findFirst("span.swift_inner_message");
assert(!replaceContent.isNull());
QString old = replaceContent.toOuterXml();
replaceContent.setInnerXml(ChatSnippet::escape(newMessage));
QWebElement replaceTime = message.findFirst("span.swift_time");
assert(!replaceTime.isNull());
old = replaceTime.toOuterXml();
replaceTime.setInnerXml(ChatSnippet::escape(tr("%1 edited").arg(ChatSnippet::timeToEscapedString(editTime))));
}
else {
qWarning() << "Trying to replace element with id " << id << " but it's not there.";
}
}
void QtWebKitChatView::showEmoticons(bool show) {
showEmoticons_ = show;
{
const QWebElementCollection spans = document_.findAll("span.swift_emoticon_image");
Q_FOREACH (QWebElement span, spans) {
span.setStyleProperty("display", show ? "inline" : "none");
}
}
{
const QWebElementCollection spans = document_.findAll("span.swift_emoticon_text");
Q_FOREACH (QWebElement span, spans) {
span.setStyleProperty("display", show ? "none" : "inline");
}
}
}
void QtWebKitChatView::copySelectionToClipboard() {
if (!webPage_->selectedText().isEmpty()) {
webPage_->triggerAction(QWebPage::Copy);
}
}
void QtWebKitChatView::setAckXML(const QString& id, const QString& xml) {
QWebElement message = document_.findFirst("#" + id);
/* Deliberately not asserting here, so that when we start expiring old messages it won't hit us */
if (message.isNull()) return;
QWebElement ackElement = message.findFirst("span.swift_ack");
assert(!ackElement.isNull());
ackElement.setInnerXml(xml);
}
void QtWebKitChatView::setReceiptXML(const QString& id, const QString& xml) {
QWebElement message = document_.findFirst("#" + id);
if (message.isNull()) return;
QWebElement receiptElement = message.findFirst("span.swift_receipt");
assert(!receiptElement.isNull());
receiptElement.setInnerXml(xml);
}
void QtWebKitChatView::displayReceiptInfo(const QString& id, bool showIt) {
QWebElement message = document_.findFirst("#" + id);
if (message.isNull()) return;
QWebElement receiptElement = message.findFirst("span.swift_receipt");
assert(!receiptElement.isNull());
receiptElement.setStyleProperty("display", showIt ? "inline" : "none");
}
void QtWebKitChatView::rememberScrolledToBottom() {
isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) >= (webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical) - 1);
}
void QtWebKitChatView::scrollToBottom() {
isAtBottom_ = true;
webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical));
webView_->update(); /* Work around redraw bug in some versions of Qt. */
}
void QtWebKitChatView::handleFrameSizeChanged() {
if (topMessageAdded_) {
// adjust new scrollbar position
int newMaximum = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical);
webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, newMaximum - scrollBarMaximum_);
topMessageAdded_ = false;
}
if (isAtBottom_ && !disableAutoScroll_) {
scrollToBottom();
}
}
void QtWebKitChatView::handleLinkClicked(const QUrl& url) {
QDesktopServices::openUrl(url);
}
void QtWebKitChatView::handleViewLoadFinished(bool ok) {
Q_ASSERT(ok);
viewReady_ = true;
}
void QtWebKitChatView::increaseFontSize(int numSteps) {
//qDebug() << "Increasing";
fontSizeSteps_ += numSteps;
emit fontResized(fontSizeSteps_);
}
void QtWebKitChatView::decreaseFontSize() {
fontSizeSteps_--;
if (fontSizeSteps_ < 0) {
fontSizeSteps_ = 0;
}
emit fontResized(fontSizeSteps_);
}
void QtWebKitChatView::resizeFont(int fontSizeSteps) {
fontSizeSteps_ = fontSizeSteps;
double size = 1.0 + 0.2 * fontSizeSteps_;
QString sizeString(QString().setNum(size, 'g', 3) + "em");
//qDebug() << "Setting to " << sizeString;
const QWebElementCollection spans = document_.findAll("span.swift_resizable");
Q_FOREACH (QWebElement span, spans) {
span.setStyleProperty("font-size", sizeString);
}
webView_->setFontSizeIsMinimal(size == 1.0);
}
void QtWebKitChatView::resetView() {
lastElement_ = QWebElement();
firstElement_ = lastElement_;
topMessageAdded_ = false;
scrollBarMaximum_ = 0;
QString pageHTML = theme_->getTemplate();
pageHTML.replace("==bodyBackground==", "background-color:#e3e3e3");
pageHTML.replace(pageHTML.indexOf("%@"), 2, theme_->getBase());
if (pageHTML.count("%@") > 3) {
pageHTML.replace(pageHTML.indexOf("%@"), 2, theme_->getMainCSS());
}
pageHTML.replace(pageHTML.indexOf("%@"), 2, "Variants/Blue on Green.css");
pageHTML.replace(pageHTML.indexOf("%@"), 2, ""/*headerSnippet.getContent()*/);
pageHTML.replace(pageHTML.indexOf("%@"), 2, ""/*footerSnippet.getContent()*/);
QEventLoop syncLoop;
connect(webView_, SIGNAL(loadFinished(bool)), &syncLoop, SLOT(quit()));
webPage_->mainFrame()->setHtml(pageHTML);
while (!viewReady_) {
QTimer t;
t.setSingleShot(true);
connect(&t, SIGNAL(timeout()), &syncLoop, SLOT(quit()));
t.start(50);
syncLoop.exec();
}
document_ = webPage_->mainFrame()->documentElement();
resetTopInsertPoint();
QWebElement chatElement = document_.findFirst("#Chat");
newInsertPoint_ = chatElement.clone();
newInsertPoint_.setOuterXml("<div id='swift_insert'/>");
chatElement.appendInside(newInsertPoint_);
Q_ASSERT(!newInsertPoint_.isNull());
scrollToBottom();
connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection);
}
static QWebElement findElementWithID(QWebElement document, QString elementName, QString id) {
QWebElementCollection elements = document.findAll(elementName);
Q_FOREACH(QWebElement element, elements) {
if (element.attribute("id") == id) {
return element;
}
}
return QWebElement();
}
void QtWebKitChatView::setFileTransferProgress(QString id, const int percentageDone) {
QWebElement ftElement = findElementWithID(document_, "div", id);
if (ftElement.isNull()) {
SWIFT_LOG(debug) << "Tried to access FT UI via invalid id!" << std::endl;
return;
}
QWebElement progressBar = ftElement.findFirst("div.progressbar");
progressBar.setStyleProperty("width", QString::number(percentageDone) + "%");
QWebElement progressBarValue = ftElement.findFirst("div.progressbar-value");
progressBarValue.setInnerXml(QString::number(percentageDone) + " %");
}
void QtWebKitChatView::setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& /* msg */) {
QWebElement ftElement = findElementWithID(document_, "div", id);
if (ftElement.isNull()) {
SWIFT_LOG(debug) << "Tried to access FT UI via invalid id! id = " << Q2PSTRING(id) << std::endl;
return;
}
QString newInnerHTML = "";
if (state == ChatWindow::WaitingForAccept) {
newInnerHTML = tr("Waiting for other side to accept the transfer.") + "<br/>" +
buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id);
}
if (state == ChatWindow::Negotiating) {
// replace with text "Negotiaging" + Cancel button
newInnerHTML = tr("Negotiating...") + "<br/>" +
buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id);
}
else if (state == ChatWindow::Transferring) {
// progress bar + Cancel Button
newInnerHTML = "<div style=\"position: relative; width: 90%; height: 20px; border: 2px solid grey; -webkit-border-radius: 10px;\">"
"<div class=\"progressbar\" style=\"width: 0%; height: 100%; background: #AAA; -webkit-border-radius: 6px;\">"
"<div class=\"progressbar-value\" style=\"position: absolute; top: 0px; left: 0px; width: 100%; text-align: center; padding-top: 2px;\">"
"0%"
"</div>"
"</div>"
"</div>" +
buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id);
}
else if (state == ChatWindow::Canceled) {
newInnerHTML = tr("Transfer has been canceled!");
}
else if (state == ChatWindow::Finished) {
// text "Successful transfer"
newInnerHTML = tr("Transfer completed successfully.");
}
else if (state == ChatWindow::FTFailed) {
newInnerHTML = tr("Transfer failed.");
}
ftElement.setInnerXml(newInnerHTML);
}
void QtWebKitChatView::setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state) {
QWebElement divElement = findElementWithID(document_, "div", id);
QString newInnerHTML;
if (state == ChatWindow::WhiteboardAccepted) {
newInnerHTML = tr("Started whiteboard chat") + "<br/>" + buildChatWindowButton(tr("Show whiteboard"), ButtonWhiteboardShowWindow, id);
} else if (state == ChatWindow::WhiteboardTerminated) {
newInnerHTML = tr("Whiteboard chat has been canceled");
} else if (state == ChatWindow::WhiteboardRejected) {
newInnerHTML = tr("Whiteboard chat request has been rejected");
}
divElement.setInnerXml(newInnerHTML);
}
void QtWebKitChatView::setMUCInvitationJoined(QString id) {
QWebElement divElement = findElementWithID(document_, "div", id);
QWebElement buttonElement = findElementWithID(divElement, "input", "mucinvite");
if (!buttonElement.isNull()) {
buttonElement.setAttribute("value", tr("Return to room"));
}
}
void QtWebKitChatView::handleScrollRequested(int, int dy, const QRect&) {
rememberScrolledToBottom();
int pos = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) - dy;
emit scrollRequested(pos);
if (pos == 0) {
emit scrollReachedTop();
}
else if (pos == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)) {
emit scrollReachedBottom();
}
}
int QtWebKitChatView::getSnippetPositionByDate(const QDate& date) {
QWebElement message = webPage_->mainFrame()->documentElement().findFirst(".date" + date.toString(Qt::ISODate));
return message.geometry().top();
}
void QtWebKitChatView::resetTopInsertPoint() {
QWebElement continuationElement = firstElement_.findFirst("#insert");
continuationElement.removeFromDocument();
firstElement_ = QWebElement();
topInsertPoint_.removeFromDocument();
QWebElement chatElement = document_.findFirst("#Chat");
topInsertPoint_ = chatElement.clone();
topInsertPoint_.setOuterXml("<div id='swift_insert'/>");
chatElement.prependInside(topInsertPoint_);
}
std::string QtWebKitChatView::addMessage(
const ChatWindow::ChatMessage& message,
const std::string& senderName,
bool senderIsSelf,
boost::shared_ptr<SecurityLabel> label,
const std::string& avatarPath,
const boost::posix_time::ptime& time,
const HighlightAction& highlight) {
return addMessage(chatMessageToHTML(message), senderName, senderIsSelf, label, avatarPath, "", time, highlight, ChatSnippet::getDirection(message));
}
QString QtWebKitChatView::getHighlightSpanStart(const std::string& text, const std::string& background) {
QString ecsapeColor = QtUtilities::htmlEscape(P2QSTRING(text));
QString escapeBackground = QtUtilities::htmlEscape(P2QSTRING(background));
if (ecsapeColor.isEmpty()) {
ecsapeColor = "black";
}
if (escapeBackground.isEmpty()) {
escapeBackground = "yellow";
}
return QString("<span style=\"color: %1; background: %2\">").arg(ecsapeColor).arg(escapeBackground);
}
QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) {
return getHighlightSpanStart(highlight.getTextColor(), highlight.getTextBackground());
}
QString QtWebKitChatView::chatMessageToHTML(const ChatWindow::ChatMessage& message) {
QString result;
foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, message.getParts()) {
boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
boost::shared_ptr<ChatWindow::ChatURIMessagePart> uriPart;
boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart;
boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart;
if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
QString text = QtUtilities::htmlEscape(P2QSTRING(textPart->text));
text.replace("\n","<br/>");
result += text;
continue;
}
if ((uriPart = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(part))) {
QString uri = QtUtilities::htmlEscape(P2QSTRING(uriPart->target));
result += "<a href='" + uri + "' >" + uri + "</a>";
continue;
}
if ((emoticonPart = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(part))) {
QString textStyle = showEmoticons_ ? "style='display:none'" : "";
QString imageStyle = showEmoticons_ ? "" : "style='display:none'";
result += "<span class='swift_emoticon_image' " + imageStyle + "><img src='" + P2QSTRING(emoticonPart->imagePath) + "'/></span><span class='swift_emoticon_text' " + textStyle + ">" + QtUtilities::htmlEscape(P2QSTRING(emoticonPart->alternativeText)) + "</span>";
continue;
}
if ((highlightPart = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part))) {
QString spanStart = getHighlightSpanStart(highlightPart->foregroundColor, highlightPart->backgroundColor);
result += spanStart + QtUtilities::htmlEscape(P2QSTRING(highlightPart->text)) + "</span>";
continue;
}
}
return result;
}
std::string QtWebKitChatView::addMessage(
const QString& message,
const std::string& senderName,
bool senderIsSelf,
boost::shared_ptr<SecurityLabel> label,
const std::string& avatarPath,
const QString& style,
const boost::posix_time::ptime& time,
const HighlightAction& highlight,
ChatSnippet::Direction direction) {
QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str());
QString htmlString;
if (label) {
htmlString = QString("<span style=\"border: thin dashed grey; padding-left: .5em; padding-right: .5em; color: %1; background-color: %2; font-size: 90%; margin-right: .5em; \" class='swift_label'>").arg(QtUtilities::htmlEscape(P2QSTRING(label->getForegroundColor()))).arg(QtUtilities::htmlEscape(P2QSTRING(label->getBackgroundColor())));
htmlString += QString("%1</span> ").arg(QtUtilities::htmlEscape(P2QSTRING(label->getDisplayMarking())));
}
QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">";
QString styleSpanEnd = style == "" ? "" : "</span>";
- QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : "";
- QString highlightSpanEnd = highlight.highlightText() ? "</span>" : "";
+ QString highlightSpanStart = highlight.highlightAllText() ? getHighlightSpanStart(highlight) : "";
+ QString highlightSpanEnd = highlight.highlightAllText() ? "</span>" : "";
htmlString += "<span class='swift_inner_message'>" + styleSpanStart + highlightSpanStart + message + highlightSpanEnd + styleSpanEnd + "</span>" ;
bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf);
QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded();
std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++);
addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), direction));
previousMessageWasSelf_ = senderIsSelf;
previousSenderName_ = P2QSTRING(senderName);
previousMessageKind_ = PreviousMessageWasMessage;
return id;
}
std::string QtWebKitChatView::addAction(const ChatWindow::ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
return addMessage(" *" + chatMessageToHTML(message) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time, highlight, ChatSnippet::getDirection(message));
}
static QString encodeButtonArgument(const QString& str) {
return QtUtilities::htmlEscape(P2QSTRING(Base64::encode(createByteArray(Q2PSTRING(str)))));
}
static QString decodeButtonArgument(const QString& str) {
return P2QSTRING(byteArrayToString(Base64::decode(Q2PSTRING(str))));
}
QString QtWebKitChatView::buildChatWindowButton(const QString& name, const QString& id, const QString& arg1, const QString& arg2, const QString& arg3, const QString& arg4, const QString& arg5) {
QRegExp regex("[A-Za-z][A-Za-z0-9\\-\\_]+");
Q_ASSERT(regex.exactMatch(id));
QString html = QString("<input id='%2' type='submit' value='%1' onclick='chatwindow.buttonClicked(\"%2\", \"%3\", \"%4\", \"%5\", \"%6\", \"%7\");' />").arg(name).arg(id).arg(encodeButtonArgument(arg1)).arg(encodeButtonArgument(arg2)).arg(encodeButtonArgument(arg3)).arg(encodeButtonArgument(arg4)).arg(encodeButtonArgument(arg5));
return html;
}
std::string QtWebKitChatView::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) {
SWIFT_LOG(debug) << "addFileTransfer" << std::endl;
QString ft_id = QString("ft%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
QString actionText;
QString htmlString;
QString formattedFileSize = P2QSTRING(formatSize(sizeInBytes));
if (senderIsSelf) {
// outgoing
actionText = tr("Send file");
htmlString = actionText + ": " + P2QSTRING(filename) + " ( " + formattedFileSize + ") <br/>" +
"<div id='" + ft_id + "'>" +
buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) +
buildChatWindowButton(tr("Set Description"), ButtonFileTransferSetDescription, ft_id) +
buildChatWindowButton(tr("Send"), ButtonFileTransferSendRequest, ft_id) +
"</div>";
} else {
// incoming
actionText = tr("Receiving file");
htmlString = actionText + ": " + P2QSTRING(filename) + " ( " + formattedFileSize + ") <br/>" +
"<div id='" + ft_id + "'>" +
buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) +
buildChatWindowButton(tr("Accept"), ButtonFileTransferAcceptRequest, ft_id, P2QSTRING(filename)) +
"</div>";
}
//addMessage(message, senderName, senderIsSelf, boost::shared_ptr<SecurityLabel>(), "", boost::posix_time::second_clock::local_time());
bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasFileTransfer, senderName, senderIsSelf);
QString qAvatarPath = "qrc:/icons/avatar.png";
std::string id = "ftmessage" + boost::lexical_cast<std::string>(idCounter_++);
addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), ChatSnippet::getDirection(actionText)));
previousMessageWasSelf_ = senderIsSelf;
previousSenderName_ = P2QSTRING(senderName);
previousMessageKind_ = PreviousMessageWasFileTransfer;
return Q2PSTRING(ft_id);
}
void QtWebKitChatView::setFileTransferProgress(std::string id, const int percentageDone) {
setFileTransferProgress(P2QSTRING(id), percentageDone);
}
void QtWebKitChatView::setFileTransferStatus(std::string id, const ChatWindow::FileTransferState state, const std::string& msg) {
setFileTransferStatus(P2QSTRING(id), state, P2QSTRING(msg));
}
std::string QtWebKitChatView::addWhiteboardRequest(const QString& contact, bool senderIsSelf) {
QString wb_id = QString("wb%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
QString htmlString;
QString actionText;
if (senderIsSelf) {
actionText = tr("Starting whiteboard chat");
htmlString = "<div id='" + wb_id + "'>" + actionText + "<br />"+
buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) +
"</div>";
} else {
actionText = tr("%1 would like to start a whiteboard chat");
htmlString = "<div id='" + wb_id + "'>" + actionText.arg(QtUtilities::htmlEscape(contact)) + ": <br/>" +
buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) +
buildChatWindowButton(tr("Accept"), ButtonWhiteboardSessionAcceptRequest, wb_id) +
"</div>";
}
QString qAvatarPath = "qrc:/icons/avatar.png";
std::string id = "wbmessage" + boost::lexical_cast<std::string>(idCounter_++);
addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(contact), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, false, theme_, P2QSTRING(id), ChatSnippet::getDirection(actionText)));
previousMessageWasSelf_ = false;
previousSenderName_ = contact;
return Q2PSTRING(wb_id);
}
void QtWebKitChatView::setWhiteboardSessionStatus(const std::string& id, const ChatWindow::WhiteboardSessionState state) {
setWhiteboardSessionStatus(P2QSTRING(id), state);
}
void QtWebKitChatView::handleHTMLButtonClicked(QString id, QString encodedArgument1, QString encodedArgument2, QString encodedArgument3, QString encodedArgument4, QString encodedArgument5) {
QString arg1 = decodeButtonArgument(encodedArgument1);
QString arg2 = decodeButtonArgument(encodedArgument2);
QString arg3 = decodeButtonArgument(encodedArgument3);
QString arg4 = decodeButtonArgument(encodedArgument4);
QString arg5 = decodeButtonArgument(encodedArgument5);
if (id.startsWith(ButtonFileTransferCancel)) {
QString ft_id = arg1;
window_->onFileTransferCancel(Q2PSTRING(ft_id));
}
else if (id.startsWith(ButtonFileTransferSetDescription)) {
QString ft_id = arg1;
bool ok = false;
QString text = QInputDialog::getText(this, tr("File transfer description"),
tr("Description:"), QLineEdit::Normal, "", &ok);
if (ok) {
descriptions_[ft_id] = text;
}
}
else if (id.startsWith(ButtonFileTransferSendRequest)) {
QString ft_id = arg1;
QString text = descriptions_.find(ft_id) == descriptions_.end() ? QString() : descriptions_[ft_id];
window_->onFileTransferStart(Q2PSTRING(ft_id), Q2PSTRING(text));
}
else if (id.startsWith(ButtonFileTransferAcceptRequest)) {
QString ft_id = arg1;
QString filename = arg2;
QString path = QFileDialog::getSaveFileName(this, tr("Save File"), filename);
if (!path.isEmpty()) {
window_->onFileTransferAccept(Q2PSTRING(ft_id), Q2PSTRING(path));
}
}
else if (id.startsWith(ButtonWhiteboardSessionAcceptRequest)) {
QString id = arg1;
setWhiteboardSessionStatus(id, ChatWindow::WhiteboardAccepted);
window_->onWhiteboardSessionAccept();
}
else if (id.startsWith(ButtonWhiteboardSessionCancel)) {
QString id = arg1;
setWhiteboardSessionStatus(id, ChatWindow::WhiteboardTerminated);
window_->onWhiteboardSessionCancel();
}
else if (id.startsWith(ButtonWhiteboardShowWindow)) {
QString id = arg1;
window_->onWhiteboardWindowShow();
}
else if (id.startsWith(ButtonMUCInvite)) {
QString roomJID = arg1;
QString password = arg2;
QString elementID = arg3;
QString isImpromptu = arg4;
QString isContinuation = arg5;
eventStream_->send(boost::make_shared<JoinMUCUIEvent>(Q2PSTRING(roomJID), Q2PSTRING(password), boost::optional<std::string>(), false, false, isImpromptu.contains("true"), isContinuation.contains("true")));
setMUCInvitationJoined(elementID);
}
else {
SWIFT_LOG(debug) << "Unknown HTML button! ( " << Q2PSTRING(id) << " )" << std::endl;
}
}
void QtWebKitChatView::addErrorMessage(const ChatWindow::ChatMessage& errorMessage) {
if (window_->isWidgetSelected()) {
window_->onAllMessagesRead();
}
QString errorMessageHTML(chatMessageToHTML(errorMessage));
addMessageBottom(boost::make_shared<SystemMessageSnippet>("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_, ChatSnippet::getDirection(errorMessage)));
previousMessageWasSelf_ = false;
previousMessageKind_ = PreviousMessageWasSystem;
}
void QtWebKitChatView::addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) {
if (window_->isWidgetSelected()) {
window_->onAllMessagesRead();
}
QString messageHTML = chatMessageToHTML(message);
addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction)));
previousMessageKind_ = PreviousMessageWasSystem;
}
void QtWebKitChatView::replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
replaceMessage(" *" + chatMessageToHTML(message) + "*", id, time, "font-style:italic ", highlight);
}
void QtWebKitChatView::replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
replaceMessage(chatMessageToHTML(message), id, time, "", highlight);
}
void QtWebKitChatView::replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style, const HighlightAction& highlight) {
if (!id.empty()) {
if (window_->isWidgetSelected()) {
window_->onAllMessagesRead();
}
QString messageHTML(message);
QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">";
QString styleSpanEnd = style == "" ? "" : "</span>";
- QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : "";
- QString highlightSpanEnd = highlight.highlightText() ? "</span>" : "";
+ QString highlightSpanStart = highlight.highlightAllText() ? getHighlightSpanStart(highlight) : "";
+ QString highlightSpanEnd = highlight.highlightAllText() ? "</span>" : "";
messageHTML = styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd;
replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time));
}
else {
std::cerr << "Trying to replace a message with no id";
}
}
void QtWebKitChatView::addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) {
if (window_->isWidgetSelected()) {
window_->onAllMessagesRead();
}
QString messageHTML = chatMessageToHTML(message);
addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction)));
previousMessageKind_ = PreviousMessageWasPresence;
}
void QtWebKitChatView::replaceLastMessage(const ChatWindow::ChatMessage& message, const ChatWindow::TimestampBehaviour timestampBehaviour) {
replaceLastMessage(chatMessageToHTML(message), timestampBehaviour);
}
void QtWebKitChatView::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) {
if (window_->isWidgetSelected()) {
window_->onAllMessagesRead();
}
QString message;
if (isImpromptu) {
message = QObject::tr("You've been invited to join a chat.") + "\n";
} else {
message = QObject::tr("You've been invited to enter the %1 room.").arg(P2QSTRING(jid.toString())) + "\n";
}
QString htmlString = message;
if (!reason.empty()) {
htmlString += QObject::tr("Reason: %1").arg(P2QSTRING(reason)) + "\n";
}
if (!direct) {
htmlString += QObject::tr("This person may not have really sent this invitation!") + "\n";
}
htmlString = chatMessageToHTML(ChatWindow::ChatMessage(Q2PSTRING(htmlString)));
QString id = QString(ButtonMUCInvite + "%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
htmlString += "<div id='" + id + "'>" +
buildChatWindowButton(chatMessageToHTML(ChatWindow::ChatMessage(Q2PSTRING((tr("Accept Invite"))))), ButtonMUCInvite, QtUtilities::htmlEscape(P2QSTRING(jid.toString())), QtUtilities::htmlEscape(P2QSTRING(password)), id, QtUtilities::htmlEscape(isImpromptu ? "true" : "false"), QtUtilities::htmlEscape(isContinuation ? "true" : "false")) +
"</div>";
bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMUCInvite, senderName, false);
QString qAvatarPath = "qrc:/icons/avatar.png";
addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, appendToPrevious, theme_, id, ChatSnippet::getDirection(message)));
previousMessageWasSelf_ = false;
previousSenderName_ = P2QSTRING(senderName);
previousMessageKind_ = PreviousMessageWasMUCInvite;
}
void QtWebKitChatView::setAckState(std::string const& id, ChatWindow::AckState state) {
QString xml;
switch (state) {
case ChatWindow::Pending:
xml = "<img src='qrc:/icons/throbber.gif' title='" + tr("This message has not been received by your server yet.") + "'/>";
displayReceiptInfo(P2QSTRING(id), false);
break;
case ChatWindow::Received:
xml = "";
displayReceiptInfo(P2QSTRING(id), true);
break;
case ChatWindow::Failed: xml = "<img src='qrc:/icons/error.png' title='" + tr("This message may not have been transmitted.") + "'/>"; break;
}
setAckXML(P2QSTRING(id), xml);
}
void QtWebKitChatView::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) {
QString xml;
switch (state) {
case ChatWindow::ReceiptReceived:
xml = "<img src='qrc:/icons/check.png' title='" + tr("The receipt for this message has been received.") + "'/>";
break;
case ChatWindow::ReceiptRequested:
xml = "<img src='qrc:/icons/warn.png' title='" + tr("The receipt for this message has not yet been received. The recipient(s) might not have received this message.") + "'/>";
break;
case ChatWindow::ReceiptFailed:
xml = "<img src='qrc:/icons/error.png' title='" + tr("Failed to transmit message to the receipient(s).") + "'/>";
}
setReceiptXML(P2QSTRING(id), xml);
}
bool QtWebKitChatView::appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) {
bool result = previousMessageKind_ == messageKind && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_&& previousSenderName_ == P2QSTRING(senderName)));
if (insertingLastLine_) {
insertingLastLine_ = false;
return false;
}
return result;
}
ChatSnippet::Direction QtWebKitChatView::getActualDirection(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) {
if (direction == ChatWindow::DefaultDirection) {
return QCoreApplication::translate("QApplication", "QT_LAYOUT_DIRECTION") == "RTL" ? ChatSnippet::RTL : ChatSnippet::LTR;
}
else {
return ChatSnippet::getDirection(message);
}
}
// void QtWebKitChatView::setShowEmoticons(bool value) {
// showEmoticons_ = value;
// }
}