← Back to team overview

linuxdcpp-team team mailing list archive

[Branch ~dcplusplus-team/dcplusplus/trunk] Rev 2838: make ChatMessage movable outside of the CM lock; color nicks in chat

 

------------------------------------------------------------
revno: 2838
committer: poy <poy@xxxxxxxxxx>
branch nick: trunk
timestamp: Fri 2012-01-27 23:19:56 +0100
message:
  make ChatMessage movable outside of the CM lock; color nicks in chat
removed:
  dcpp/ChatMessage.cpp
modified:
  changelog.txt
  dcpp/AdcHub.cpp
  dcpp/ChatMessage.h
  dcpp/ClientListener.h
  dcpp/NmdcHub.cpp
  dwt/include/dwt/widgets/RichTextBox.h
  dwt/src/widgets/RichTextBox.cpp
  win32/AspectChat.h
  win32/ConnectivityPage.cpp
  win32/HubFrame.cpp
  win32/HubFrame.h
  win32/PrivateFrame.cpp
  win32/PrivateFrame.h


--
lp:dcplusplus
https://code.launchpad.net/~dcplusplus-team/dcplusplus/trunk

Your team Dcplusplus-team is subscribed to branch lp:dcplusplus.
To unsubscribe from this branch go to https://code.launchpad.net/~dcplusplus-team/dcplusplus/trunk/+edit-subscription
=== modified file 'changelog.txt'
--- changelog.txt	2012-01-25 18:21:31 +0000
+++ changelog.txt	2012-01-27 22:19:56 +0000
@@ -15,6 +15,7 @@
 * Allow empty user matching definitions that match every user (poy)
 * Add predefined user matching defs for favs (bold, more red) & ops (more blue) (poy)
 * [L#300971] Keep updating GUI elements while a menu is up (poy)
+* Color nicks in chats according to user matching definitions (poy)
 
 -- 0.791 2012-01-14 --
 * Update translations

=== modified file 'dcpp/AdcHub.cpp'
--- dcpp/AdcHub.cpp	2012-01-13 20:55:20 +0000
+++ dcpp/AdcHub.cpp	2012-01-27 22:19:56 +0000
@@ -255,17 +255,19 @@
 	if(!from || from->getIdentity().noChat())
 		return;
 
-	ChatMessage message = { c.getParam(0), from };
+	ChatMessage message = { c.getParam(0), from->getUser() };
 
 	string temp;
 	if(c.getParam("PM", 1, temp)) { // add PM<group-cid> as well
-		message.to = findUser(c.getTo());
-		if(!message.to)
+		auto ou = findUser(c.getTo());
+		if(!ou)
 			return;
+		message.to = ou->getUser();
 
-		message.replyTo = findUser(AdcCommand::toSID(temp));
-		if(!message.replyTo)
+		ou = findUser(AdcCommand::toSID(temp));
+		if(!ou)
 			return;
+		message.replyTo = ou->getUser();
 	}
 
 	message.thirdPerson = c.hasFlag("ME", 1);
@@ -273,7 +275,7 @@
 	if(c.getParam("TS", 1, temp))
 		message.timestamp = Util::toInt64(temp);
 
-	fire(ClientListener::Message(), this, message);
+	fire(ClientListener::Message(), this, std::move(message));
 }
 
 void AdcHub::handle(AdcCommand::GPA, AdcCommand& c) noexcept {
@@ -498,8 +500,8 @@
 		}
 	}
 
-	ChatMessage message = { c.getParam(1), u };
-	fire(ClientListener::Message(), this, message);
+	ChatMessage message = { c.getParam(1), u->getUser() };
+	fire(ClientListener::Message(), this, std::move(message));
 }
 
 void AdcHub::handle(AdcCommand::SCH, AdcCommand& c) noexcept {

=== removed file 'dcpp/ChatMessage.cpp'
--- dcpp/ChatMessage.cpp	2012-01-13 20:55:20 +0000
+++ dcpp/ChatMessage.cpp	1970-01-01 00:00:00 +0000
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2001-2012 Jacek Sieka, arnetheduck on gmail point com
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
-#include "stdinc.h"
-#include "ChatMessage.h"
-
-#include "format.h"
-#include "OnlineUser.h"
-#include "Util.h"
-
-namespace dcpp {
-
-string ChatMessage::format() const {
-	string tmp;
-
-	if(timestamp) {
-		tmp += '[';
-		tmp += _("Sent ") + Util::getShortTimeString(timestamp) + "] ";
-	}
-
-	const string& nick = from->getIdentity().getNick();
-	// let's *not* obey the spec here and add a space after the star. :P
-	tmp += (thirdPerson ? "* " + nick + ' ' : '<' + nick + "> ") + text;
-
-	// Check all '<' and '[' after newlines as they're probably pastes...
-	size_t i = 0;
-	while( (i = tmp.find('\n', i)) != string::npos) {
-		if(i + 1 < tmp.length()) {
-			if(tmp[i+1] == '[' || tmp[i+1] == '<') {
-				tmp.insert(i+1, "- ");
-				i += 2;
-			}
-		}
-		i++;
-	}
-
-	return Text::toDOS(tmp);
-}
-
-} // namespace dcpp

=== modified file 'dcpp/ChatMessage.h'
--- dcpp/ChatMessage.h	2012-01-13 20:55:20 +0000
+++ dcpp/ChatMessage.h	2012-01-27 22:19:56 +0000
@@ -30,14 +30,12 @@
 struct ChatMessage {
 	string text;
 
-	const OnlineUser* from;
-	const OnlineUser* to;
-	const OnlineUser* replyTo;
+	UserPtr from;
+	UserPtr to;
+	UserPtr replyTo;
 
 	bool thirdPerson;
 	time_t timestamp;
-
-	string format() const;
 };
 
 } // namespace dcpp

=== modified file 'dcpp/ClientListener.h'
--- dcpp/ClientListener.h	2012-01-13 20:55:20 +0000
+++ dcpp/ClientListener.h	2012-01-27 22:19:56 +0000
@@ -61,7 +61,7 @@
 	virtual void on(Failed, Client*, const string&) noexcept { }
 	virtual void on(GetPassword, Client*) noexcept { }
 	virtual void on(HubUpdated, Client*) noexcept { }
-	virtual void on(Message, Client*, const ChatMessage&) noexcept { }
+	virtual void on(Message, Client*, ChatMessage&&) noexcept { }
 	virtual void on(StatusMessage, Client*, const string&, int = FLAG_NORMAL) noexcept { }
 	virtual void on(HubUserCommand, Client*, int, int, const string&, const string&) noexcept { }
 	virtual void on(HubFull, Client*) noexcept { }

=== modified file 'dcpp/NmdcHub.cpp'
--- dcpp/NmdcHub.cpp	2012-01-23 17:56:18 +0000
+++ dcpp/NmdcHub.cpp	2012-01-27 22:19:56 +0000
@@ -215,19 +215,17 @@
 		if(from && from->getIdentity().noChat())
 			return;
 
-		ChatMessage chatMessage = { unescape(message), from };
-
-		if(!chatMessage.from) {
+		if(!from) {
 			OnlineUser& o = getUser(nick);
 			// Assume that messages from unknown users come from the hub
 			o.getIdentity().setHub(true);
 			o.getIdentity().setHidden(true);
 			updated(o);
-
-			chatMessage.from = &o;
+			from = &o;
 		}
 
-		fire(ClientListener::Message(), this, chatMessage);
+		ChatMessage chatMessage = { unescape(message), from->getUser() };
+		fire(ClientListener::Message(), this, std::move(chatMessage));
 		return;
 	}
 
@@ -711,30 +709,29 @@
 		if(from && from->getIdentity().noChat())
 			return;
 
-		ChatMessage message = { unescape(param.substr(j + 2)), from, &getUser(getMyNick()), findUser(rtNick) };
-
-		if(!message.replyTo || !message.from) {
-			if(!message.replyTo) {
-				// Assume it's from the hub
-				OnlineUser& replyTo = getUser(rtNick);
-				replyTo.getIdentity().setHub(true);
-				replyTo.getIdentity().setHidden(true);
-				updated(replyTo);
-			}
-			if(!message.from) {
-				// Assume it's from the hub
-				OnlineUser& from = getUser(fromNick);
-				from.getIdentity().setHub(true);
-				from.getIdentity().setHidden(true);
-				updated(from);
-			}
-
-			// Update pointers just in case they've been invalidated
-			message.replyTo = findUser(rtNick);
-			message.from = findUser(fromNick);
+		auto replyTo = findUser(rtNick);
+
+		if(!replyTo || !from) {
+			if(!replyTo) {
+				// Assume it's from the hub
+				OnlineUser& ou = getUser(rtNick);
+				ou.getIdentity().setHub(true);
+				ou.getIdentity().setHidden(true);
+				updated(ou);
+				replyTo = &ou;
+			}
+			if(!from) {
+				// Assume it's from the hub
+				OnlineUser& ou = getUser(fromNick);
+				ou.getIdentity().setHub(true);
+				ou.getIdentity().setHidden(true);
+				updated(ou);
+				from = &ou;
+			}
 		}
 
-		fire(ClientListener::Message(), this, message);
+		ChatMessage message = { unescape(param.substr(j + 2)), from->getUser(), getUser(getMyNick()).getUser(), replyTo->getUser() };
+		fire(ClientListener::Message(), this, std::move(message));
 
 	} else if(cmd == "$GetPass") {
 		OnlineUser& ou = getUser(getMyNick());
@@ -917,8 +914,8 @@
 	Lock l(cs);
 	OnlineUser* ou = findUser(getMyNick());
 	if(ou) {
-		ChatMessage message = { aMessage, ou, &aUser, ou };
-		fire(ClientListener::Message(), this, message);
+		ChatMessage message = { aMessage, ou->getUser(), aUser.getUser(), ou->getUser() };
+		fire(ClientListener::Message(), this, std::move(message));
 	}
 }
 

=== modified file 'dwt/include/dwt/widgets/RichTextBox.h'
--- dwt/include/dwt/widgets/RichTextBox.h	2012-01-23 17:56:18 +0000
+++ dwt/include/dwt/widgets/RichTextBox.h	2012-01-27 22:19:56 +0000
@@ -114,7 +114,7 @@
 	  */
 	void addText( const std::string & txt );
 
-	void addTextSteady ( const tstring & txtRaw, std::size_t len );
+	void addTextSteady(const tstring& txtRaw);
 
 	void findText(tstring const& needle);
 

=== modified file 'dwt/src/widgets/RichTextBox.cpp'
--- dwt/src/widgets/RichTextBox.cpp	2012-01-13 20:55:20 +0000
+++ dwt/src/widgets/RichTextBox.cpp	2012-01-27 22:19:56 +0000
@@ -196,7 +196,7 @@
 	setTextEx(txt, ST_SELECTION);
 }
 
-void RichTextBox::addTextSteady( const tstring & txtRaw, std::size_t len ) {
+void RichTextBox::addTextSteady(const tstring& txtRaw) {
 	Point scrollPos = getScrollPos();
 	bool scroll = scrollIsAtEnd();
 
@@ -208,6 +208,9 @@
 		unsigned charsRemoved = 0;
 		int multipler = 1;
 
+		/* this will include more chars than there actually are because of RTF codes. not a problem
+		here; accuracy isn't necessary since whole lines are getting chopped anyway. */
+		size_t len = txtRaw.size();
 		size_t limit = getTextLimit();
 		if(length() + len > limit) {
 			util::HoldRedraw hold2(this, scroll);

=== modified file 'win32/AspectChat.h'
--- win32/AspectChat.h	2012-01-13 20:55:20 +0000
+++ win32/AspectChat.h	2012-01-27 22:19:56 +0000
@@ -62,7 +62,7 @@
 
 		t().addAccel(FALT, 'C', [this] { this->chat->setFocus(); });
 		t().addAccel(FALT, 'M', [this] { this->message->setFocus(); });
-		t().addAccel(FALT, 'S', [this] { this->sendMessage_(); });
+		t().addAccel(FALT, 'S', [this] { this->sendMessage(); });
 		t().addAccel(0, VK_ESCAPE, [this] { this->handleEscape(); });
 		t().addAccel(FCONTROL, 'F', [this] { this->chat->findTextNew(); });
 		t().addAccel(0, VK_F3, [this] { this->chat->findTextNext(); });
@@ -70,20 +70,28 @@
 
 	virtual ~AspectChat() { }
 
-	tstring formatChatMessage(Client* aClient, const tstring& aLine) {
+	/* the first tstring is a plain formatted chat message (no style); the second tstring is a
+	formatted message with style codes. "formatted" means that the message has been prepended with
+	the sender's nick and possibly a timestamp, new lines have been converted to CR-LF and other
+	smoothing may have been applied. */
+	typedef std::pair<tstring, tstring> FormattedChatMessage;
+
+	tstring formatText(const tstring& aLine) {
 		/// @todo factor out to dwt
 		/// @todo Text::toT works but _T doesn't, verify this.
-		return Text::toT("{\\urtf1\n") + chat->rtfEscape(aLine) + Text::toT("}\n");
+		return Text::toT("{\\urtf1\n") + aLine + Text::toT("}\n");
 	}
 
-	void addChat(Client* aClient, const tstring& aLine) {
-		tstring line;
+	/** Add a chat message. The message must have already been formatted; its special RTF
+	characters must have been escaped with chat->rtfEscape. There is one exception: this method
+	handles encapsulating the message within {\urtf1...} and prepending timestamps. */
+	void addChat(const tstring& text) {
+		tstring pre;
 		if(chat->length() > 0)
-			line += _T("\r\n");
+			pre += _T("\r\n");
 		if(timeStamps)
-			line += Text::toT("[" + Util::getShortTimeString() + "] ");
-		line += Text::toDOS(aLine);
-		chat->addTextSteady(formatChatMessage(aClient, line), line.size());
+			pre += Text::toT("[" + Util::getShortTimeString() + "] ");
+		chat->addTextSteady(formatText(chat->rtfEscape(pre) + text));
 	}
 
 	void readLog(const string& logPath, const unsigned setting) {
@@ -110,8 +118,8 @@
 		lines.pop_back();
 
 		const size_t linesCount = lines.size();
-		for(size_t i = ((linesCount > setting) ? (linesCount - setting) : 0); i < linesCount; ++i) {
-			addChat(0, _T("- ") + Text::toT(lines[i]));
+		for(size_t i = (linesCount > setting) ? (linesCount - setting) : 0; i < linesCount; ++i) {
+			addChat(chat->rtfEscape(_T("- ") + Text::toT(lines[i])));
 		}
 	}
 
@@ -297,7 +305,6 @@
 		t().enterImpl(s);
 		return true;
 	}
-	void sendMessage_() { sendMessage(); }
 };
 
 #endif // !defined(DCPLUSPLUS_WIN32_ASPECT_CHAT_H)

=== modified file 'win32/ConnectivityPage.cpp'
--- win32/ConnectivityPage.cpp	2012-01-23 20:18:58 +0000
+++ win32/ConnectivityPage.cpp	2012-01-27 22:19:56 +0000
@@ -133,8 +133,7 @@
 
 void ConnectivityPage::addLogLine(const tstring& msg) {
 	/// @todo factor out to dwt
-	const tstring message = Text::toT("{\\urtf1\n") + log->rtfEscape(msg + Text::toT("\r\n")) + Text::toT("}\n");
-	log->addTextSteady(message, message.size());
+	log->addTextSteady(Text::toT("{\\urtf1\n") + log->rtfEscape(msg + Text::toT("\r\n")) + Text::toT("}\n"));
 }
 
 void ConnectivityPage::on(Message, const string& message) noexcept {

=== modified file 'win32/HubFrame.cpp'
--- win32/HubFrame.cpp	2012-01-22 20:27:14 +0000
+++ win32/HubFrame.cpp	2012-01-27 22:19:56 +0000
@@ -518,18 +518,100 @@
 	tasks.clear();
 }
 
-void HubFrame::addChat(const tstring& aLine) {
-	ChatType::addChat(client, aLine);
+namespace { tstring toRTF(COLORREF color) {
+	return Text::toT("\\red" + Util::toString(GetRValue(color)) +
+		"\\green" + Util::toString(GetGValue(color)) +
+		"\\blue" + Util::toString(GetBValue(color)) + ";");
+} }
+
+HubFrame::FormattedChatMessage HubFrame::format(const ChatMessage& message, int* pmInfo) const {
+	FormattedChatMessage ret;
+
+	if(message.timestamp) {
+		tstring tmp = _T("["); tmp += T_("Sent ") + Text::toT(Util::getShortTimeString(message.timestamp)) + _T("] ");
+		ret.first += tmp;
+		ret.second += chat->rtfEscape(tmp);
+	}
+
+	tstring nick;
+	Style style;
+	{
+		auto lock = ClientManager::getInstance()->lock();
+		if(message.from.get()) {
+			auto ou = ClientManager::getInstance()->findOnlineUser(message.from->getCID(), url, true);
+			if(ou) {
+				nick = Text::toT(ou->getIdentity().getNick());
+				style = ou->getIdentity().getStyle();
+			}
+		}
+
+		if(pmInfo) {
+			// gather info used by the PM dispatcher here to only use 1 CM lock.
+			auto& flags = *pmInfo;
+			auto ou = ClientManager::getInstance()->findOnlineUser(message.replyTo->getCID(), url, true);
+			if(ou && ou->getIdentity().isHub())
+				flags |= FROM_HUB;
+			if(ou && ou->getIdentity().isBot())
+				flags |= FROM_BOT;
+		}
+	}
+
+	if(!nick.empty()) {
+		// let's *not* obey the spec here and add a space after the star. :P
+		ret.first += message.thirdPerson ? _T("* ") + nick + _T(" ") : _T("<") + nick + _T("> ");
+
+		nick = chat->rtfEscape(nick);
+		if(style.textColor != -1 || style.bgColor != -1) {
+			// {{\\colortbl;\\red1\\green1\\blue1;\\red2\\green2\\blue2;}\\cf1\\cb2 nick}
+			tstring colors = _T("{{\\colortbl;");
+			tstring colSel;
+			if(style.textColor != -1) {
+				colors += toRTF(style.textColor);
+				colSel += _T("\\cf1");
+			}
+			if(style.bgColor != -1) {
+				colors += toRTF(style.bgColor);
+				colSel += (style.textColor != -1) ? _T("\\cb2") : _T("\\cb1");
+			}
+			nick = colors + _T("}") + colSel + _T(" ") + nick + _T("}");
+		}
+		ret.second += message.thirdPerson ? _T("* ") + nick + _T(" ") : _T("<") + nick + _T("> ");
+	}
+
+	// Check all '<' and '[' after newlines as they're probably pastes...
+	auto text = message.text;
+	size_t i = 0;
+	while((i = text.find('\n', i)) != string::npos) {
+		++i;
+		if(i < text.size()) {
+			if(text[i] == '[' || text[i] == '<') {
+				text.insert(i, "- ");
+				i += 2;
+			}
+		}
+	}
+
+	auto tmp = Text::toT(Text::toDOS(text));
+	ret.first += tmp;
+	ret.second += chat->rtfEscape(tmp);
+
+	return ret;
+}
+
+void HubFrame::addChat(const ChatMessage& message) {
+	auto text = format(message);
+
+	ChatType::addChat(text.second);
 
 	{
 		auto u = url;
-		WinUtil::notify(WinUtil::NOTIFICATION_MAIN_CHAT, aLine, [this, u] { activateWindow(u); });
+		WinUtil::notify(WinUtil::NOTIFICATION_MAIN_CHAT, text.first, [this, u] { activateWindow(u); });
 	}
 	setDirty(SettingsManager::BOLD_HUB);
 
 	if(BOOLSETTING(LOG_MAIN_CHAT)) {
 		ParamMap params;
-		params["message"] = [&aLine] { return Text::fromT(aLine); };
+		params["message"] = [&text] { return Text::fromT(text.first); };
 		client->getHubIdentity().getParams(params, "hub", false);
 		params["hubURL"] = [this] { return client->getHubUrl(); };
 		client->getMyIdentity().getParams(params, "my", true);
@@ -537,14 +619,18 @@
 	}
 }
 
-void HubFrame::addStatus(const tstring& aLine, bool legitimate /* = true */) {
-	tstring line = Text::toT("[" + Util::getShortTimeString() + "] ") + aLine;
+void HubFrame::addChat(const tstring& text) {
+	// this doesn't get often called so we can afford the fromT retardedness.
+	ChatMessage message = { Text::fromT(text) };
+	addChat(message);
+}
 
-	status->setText(STATUS_STATUS, line);
+void HubFrame::addStatus(const tstring& text, bool legitimate /* = true */) {
+	status->setText(STATUS_STATUS, Text::toT("[" + Util::getShortTimeString() + "] ") + text);
 
 	if(legitimate) {
 		if(BOOLSETTING(STATUS_IN_CHAT))
-			addChat(_T("*** ") + aLine);
+			addChat(_T("*** ") + text);
 		else
 			setDirty(SettingsManager::BOLD_HUB);
 	}
@@ -554,7 +640,7 @@
 		client->getHubIdentity().getParams(params, "hub", false);
 		params["hubURL"] = [this] { return client->getHubUrl(); };
 		client->getMyIdentity().getParams(params, "my", true);
-		params["message"] = [&aLine] { return Text::fromT(aLine); };
+		params["message"] = [&text] { return Text::fromT(text); };
 		LOG(LogManager::STATUS, params);
 	}
 }
@@ -632,31 +718,35 @@
 	}
 }
 
-void HubFrame::onPrivateMessage(const UserPtr& from, const UserPtr& to, const UserPtr& replyTo, bool hub, bool bot, const tstring& m) {
+void HubFrame::onPrivateMessage(const ChatMessage& message) {
+	int pmInfo = 0;
+	auto text = format(message, &pmInfo);
+
 	bool ignore = false, window = false;
-	if(hub) {
+	if((pmInfo & FROM_HUB) == FROM_HUB) {
 		if(BOOLSETTING(IGNORE_HUB_PMS)) {
 			ignore = true;
-		} else if(BOOLSETTING(POPUP_HUB_PMS) || PrivateFrame::isOpen(replyTo)) {
+		} else if(BOOLSETTING(POPUP_HUB_PMS) || PrivateFrame::isOpen(message.replyTo)) {
 			window = true;
 		}
-	} else if(bot) {
+	} else if((pmInfo & FROM_BOT) == FROM_BOT) {
 		if(BOOLSETTING(IGNORE_BOT_PMS)) {
 			ignore = true;
-		} else if(BOOLSETTING(POPUP_BOT_PMS) || PrivateFrame::isOpen(replyTo)) {
+		} else if(BOOLSETTING(POPUP_BOT_PMS) || PrivateFrame::isOpen(message.replyTo)) {
 			window = true;
 		}
-	} else if(BOOLSETTING(POPUP_PMS) || PrivateFrame::isOpen(replyTo) || from == client->getMyIdentity().getUser()) {
+	} else if(BOOLSETTING(POPUP_PMS) || PrivateFrame::isOpen(message.replyTo) || message.from == client->getMyIdentity().getUser()) {
 		window = true;
 	}
 
 	if(ignore) {
-		addStatus(str(TF_("Ignored message: %1%") % m), false);
+		addStatus(str(TF_("Ignored message: %1%") % text.first), false);
 	} else {
 		if(window) {
-			PrivateFrame::gotMessage(getParent(), from, to, replyTo, m, url);
+			PrivateFrame::gotMessage(getParent(), message.from, message.to, message.replyTo, text, url);
 		} else {
-			addChat(str(TF_("Private message from %1%: %2%") % getNick(from) % m));
+			/// @todo add formatting here (for PMs in main chat)
+			addChat(str(TF_("Private message from %1%: %2%") % getNick(message.from) % text.first));
 		}
 		WinUtil::mainWindow->TrayPM();
 	}
@@ -916,15 +1006,20 @@
 	callAsync([this, hubNameT] { setText(hubNameT); });
 }
 
-void HubFrame::on(Message, Client*, const ChatMessage& message) noexcept {
-	auto msg = Text::toT(message.format());
-	if(message.to && message.replyTo) {
-		auto from = message.from->getUser(), to = message.to->getUser(), replyTo = message.replyTo->getUser();
-		auto isHub = message.replyTo->getIdentity().isHub(), isBot = message.replyTo->getIdentity().isBot();
-		callAsync([=] { onPrivateMessage(from, to, replyTo, isHub, isBot, msg); });
-	} else {
-		callAsync([=] { addChat(msg); });
-	}
+void HubFrame::on(Message, Client*, ChatMessage&& message) noexcept {
+	struct ChatMessageFunctor {
+		ChatMessageFunctor(HubFrame* h, ChatMessage&& message) : h(h), message(std::forward<ChatMessage>(message)) { }
+		void operator()() const {
+			if(message.to.get() && message.replyTo.get()) {
+				h->onPrivateMessage(message);
+			} else {
+				h->addChat(message);
+			}
+		}
+		HubFrame* h;
+		ChatMessage message;
+	};
+	callAsync(ChatMessageFunctor(this, std::forward<ChatMessage>(message)));
 }
 
 void HubFrame::on(StatusMessage, Client*, const string& line, int statusFlags) noexcept {

=== modified file 'win32/HubFrame.h'
--- win32/HubFrame.h	2012-01-22 20:27:14 +0000
+++ win32/HubFrame.h	2012-01-27 22:19:56 +0000
@@ -197,8 +197,11 @@
 
 	bool tab();
 
-	void addChat(const tstring& aLine);
-	void addStatus(const tstring& aLine, bool legitimate = true);
+	enum { FROM_HUB = 1 << 0, FROM_BOT = 1 << 1 };
+	FormattedChatMessage format(const ChatMessage& message, int* pmInfo = nullptr) const;
+	void addChat(const ChatMessage& message);
+	void addChat(const tstring& text);
+	void addStatus(const tstring& text, bool legitimate = true);
 
 	pair<size_t, tstring> getStatusUsers() const;
 	tstring getStatusShared() const;
@@ -263,7 +266,7 @@
 	void onConnected();
 	void onDisconnected();
 	void onGetPassword();
-	void onPrivateMessage(const UserPtr& from, const UserPtr& to, const UserPtr& replyTo, bool hub, bool bot, const tstring& m);
+	void onPrivateMessage(const ChatMessage& message);
 
 	// FavoriteManagerListener
 	virtual void on(FavoriteManagerListener::UserAdded, const FavoriteUser& /*aUser*/) noexcept;
@@ -280,7 +283,7 @@
 	virtual void on(Failed, Client*, const string&) noexcept;
 	virtual void on(GetPassword, Client*) noexcept;
 	virtual void on(HubUpdated, Client*) noexcept;
-	virtual void on(Message, Client*, const ChatMessage&) noexcept;
+	virtual void on(Message, Client*, ChatMessage&&) noexcept;
 	virtual void on(StatusMessage, Client*, const string&, int = ClientListener::FLAG_NORMAL) noexcept;
 	virtual void on(NickTaken, Client*) noexcept;
 	virtual void on(SearchFlood, Client*, const string&) noexcept;

=== modified file 'win32/PrivateFrame.cpp'
--- win32/PrivateFrame.cpp	2012-01-23 20:18:58 +0000
+++ win32/PrivateFrame.cpp	2012-01-27 22:19:56 +0000
@@ -56,7 +56,7 @@
 }
 
 void PrivateFrame::gotMessage(TabViewPtr parent, const UserPtr& from, const UserPtr& to, const UserPtr& replyTo,
-							  const tstring& aMessage, const string& hubHint)
+	const FormattedChatMessage& message, const string& hubHint)
 {
 	const UserPtr& user = (replyTo == ClientManager::getInstance()->getMe()) ? to : replyTo;
 	auto i = frames.find(user);
@@ -65,7 +65,7 @@
 		if(!BOOLSETTING(POPUNDER_PM))
 			p->activate();
 
-		p->addChat(aMessage);
+		p->addChat(message);
 
 		if(Util::getAway() && !(BOOLSETTING(NO_AWAYMSG_TO_BOTS) && user->isSet(User::BOT))) {
 			auto awayMessage = Util::getAwayMessage();
@@ -74,13 +74,13 @@
 			}
 		}
 
-		WinUtil::notify(WinUtil::NOTIFICATION_PM_WINDOW, aMessage, [user] { activateWindow(user); });
+		WinUtil::notify(WinUtil::NOTIFICATION_PM_WINDOW, message.first, [user] { activateWindow(user); });
 
 	} else {
-		i->second->addChat(aMessage);
+		i->second->addChat(message);
 	}
 
-	WinUtil::notify(WinUtil::NOTIFICATION_PM, aMessage, [user] { activateWindow(user); });
+	WinUtil::notify(WinUtil::NOTIFICATION_PM, message.first, [user] { activateWindow(user); });
 }
 
 void PrivateFrame::activateWindow(const UserPtr& u) {
@@ -187,34 +187,28 @@
 PrivateFrame::~PrivateFrame() {
 }
 
-void PrivateFrame::addChat(const tstring& aLine, bool log) {
-	/// @todo null clients are allowed (eg to display log history on opening), fix later
-	Client* pClient = 0;
-	/// @todo getting the client is disabled for now (no calling findOnlineUser outside the ClientManager lock)
-	/*
-	OnlineUser *ou = ClientManager::getInstance()->findOnlineUser(*replyTo.getUser(), Util::emptyString);
-	if (ou) pClient = &(ou->getClient()); // getClient actually retuns a ref.
-	*/
-
-	ChatType::addChat(pClient, aLine);
+void PrivateFrame::addChat(const FormattedChatMessage& message, bool log) {
+	ChatType::addChat(message.second);
 
 	setDirty(SettingsManager::BOLD_PM);
 
 	if(log && BOOLSETTING(LOG_PRIVATE_CHAT)) {
 		ParamMap params;
-		params["message"] = [&aLine] { return Text::fromT(aLine); };
+		params["message"] = [&message] { return Text::fromT(message.first); };
 		fillLogParams(params);
 		LOG(LogManager::PM, params);
 	}
 }
 
-void PrivateFrame::addStatus(const tstring& aLine, bool log) {
-	tstring line = Text::toT("[" + Util::getShortTimeString() + "] ") + aLine;
+void PrivateFrame::addChat(const tstring& text, bool log) {
+	addChat(make_pair(text, chat->rtfEscape(text)), log);
+}
 
-	status->setText(STATUS_STATUS, line);
+void PrivateFrame::addStatus(const tstring& text, bool log) {
+	status->setText(STATUS_STATUS, Text::toT("[" + Util::getShortTimeString() + "] ") + text);
 
 	if(BOOLSETTING(STATUS_IN_CHAT))
-		addChat(_T("*** ") + aLine, log);
+		addChat(_T("*** ") + text, log);
 }
 
 bool PrivateFrame::preClosing() {

=== modified file 'win32/PrivateFrame.h'
--- win32/PrivateFrame.h	2012-01-22 20:27:14 +0000
+++ win32/PrivateFrame.h	2012-01-27 22:19:56 +0000
@@ -56,7 +56,7 @@
 	const string& getId() const;
 
 	static void gotMessage(TabViewPtr parent, const UserPtr& from, const UserPtr& to, const UserPtr& replyTo,
-		const tstring& aMessage, const string& hubHint);
+		const FormattedChatMessage& message, const string& hubHint);
 	static void openWindow(TabViewPtr parent, const HintedUser& replyTo, const tstring& msg = Util::emptyStringT,
 		const string& logPath = Util::emptyString, bool activate = true);
 	static void activateWindow(const UserPtr& u);
@@ -93,8 +93,9 @@
 	string getLogPath() const;
 	void openLog();
 	void fillLogParams(ParamMap& params) const;
-	void addChat(const tstring& aLine, bool log = true);
-	void addStatus(const tstring& aLine, bool log = true);
+	void addChat(const FormattedChatMessage& message, bool log = true);
+	void addChat(const tstring& text, bool log = true);
+	void addStatus(const tstring& text, bool log = true);
 	void updateOnlineStatus();
 
 	bool handleChatContextMenu(dwt::ScreenCoordinate pt);