servatrice/cockatrice/src/chatview.cpp
woogerboy21 87a64da1bc Added chat history to a room that is displayed on join.
With this update a new chat history definition is added on a per
room bases which allows operators to specify the number of chat
messages to store and present to the user on join.  Please see
the sample ini for room definitions.
2015-09-14 17:54:04 -04:00

585 lines
20 KiB
C++

#include <QTextEdit>
#include <QDateTime>
#include <QScrollBar>
#include <QMouseEvent>
#include <QDesktopServices>
#include <QApplication>
#include <QDebug>
#include "chatview.h"
#include "user_level.h"
#include "user_context_menu.h"
#include "pixmapgenerator.h"
#include "settingscache.h"
#include "tab_userlists.h"
#include "soundengine.h"
#include "room_message_type.h"
const QColor DEFAULT_MENTION_COLOR = QColor(194, 31, 47);
const QColor OTHER_USER_COLOR = QColor(0, 65, 255); // dark blue
const QString SERVER_MESSAGE_COLOR = "#851515";
ChatView::ChatView(const TabSupervisor *_tabSupervisor, TabGame *_game, bool _showTimestamps, QWidget *parent)
: QTextBrowser(parent), tabSupervisor(_tabSupervisor), game(_game), evenNumber(true), showTimestamps(_showTimestamps), hoveredItemType(HoveredNothing)
{
document()->setDefaultStyleSheet("a { text-decoration: none; color: blue; }");
userContextMenu = new UserContextMenu(tabSupervisor, this, game);
connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool)));
if(tabSupervisor->getUserInfo())
{
userName = QString::fromStdString(tabSupervisor->getUserInfo()->name());
mention = "@" + userName;
}
mentionFormat.setFontWeight(QFont::Bold);
mentionFormatOtherUser.setFontWeight(QFont::Bold);
mentionFormatOtherUser.setForeground(Qt::blue);
mentionFormatOtherUser.setAnchor(true);
viewport()->setCursor(Qt::IBeamCursor);
setReadOnly(true);
setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
setOpenLinks(false);
connect(this, SIGNAL(anchorClicked(const QUrl &)), this, SLOT(openLink(const QUrl &)));
}
void ChatView::retranslateUi()
{
userContextMenu->retranslateUi();
}
QTextCursor ChatView::prepareBlock(bool same)
{
lastSender.clear();
QTextCursor cursor(document()->lastBlock());
cursor.movePosition(QTextCursor::End);
if (same) {
cursor.insertHtml("<br>");
} else {
QTextBlockFormat blockFormat;
if ((evenNumber = !evenNumber))
blockFormat.setBackground(palette().alternateBase());
blockFormat.setBottomMargin(4);
cursor.insertBlock(blockFormat);
}
return cursor;
}
void ChatView::appendHtml(const QString &html)
{
bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum();
prepareBlock().insertHtml(html);
if (atBottom)
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}
void ChatView::appendHtmlServerMessage(const QString &html, bool optionalIsBold, QString optionalFontColor)
{
bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum();
QString htmlText = "<font color=" + ((optionalFontColor.size() > 0) ? optionalFontColor : SERVER_MESSAGE_COLOR) + ">" + html + "</font>";
if (optionalIsBold)
htmlText = "<b>" + htmlText + "</b>";
prepareBlock().insertHtml(htmlText);
if (atBottom)
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}
void ChatView::appendCardTag(QTextCursor &cursor, const QString &cardName)
{
QTextCharFormat oldFormat = cursor.charFormat();
QTextCharFormat anchorFormat = oldFormat;
anchorFormat.setForeground(Qt::blue);
anchorFormat.setAnchor(true);
anchorFormat.setAnchorHref("card://" + cardName);
anchorFormat.setFontItalic(true);
cursor.setCharFormat(anchorFormat);
cursor.insertText(cardName);
cursor.setCharFormat(oldFormat);
}
void ChatView::appendUrlTag(QTextCursor &cursor, QString url)
{
if (!url.contains("://"))
url.prepend("http://");
QTextCharFormat oldFormat = cursor.charFormat();
QTextCharFormat anchorFormat = oldFormat;
anchorFormat.setForeground(Qt::blue);
anchorFormat.setAnchor(true);
anchorFormat.setAnchorHref(url);
anchorFormat.setUnderlineColor(Qt::blue);
anchorFormat.setFontUnderline(true);
cursor.setCharFormat(anchorFormat);
cursor.insertText(url);
cursor.setCharFormat(oldFormat);
}
void ChatView::appendMessage(QString message, RoomMessageTypeFlags messageType, QString sender, UserLevelFlags userLevel, bool playerBold)
{
bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum();
bool sameSender = (sender == lastSender) && !lastSender.isEmpty();
QTextCursor cursor = prepareBlock(sameSender);
lastSender = sender;
// timestamp
if (showTimestamps && !sameSender && !sender.isEmpty()) {
QTextCharFormat timeFormat;
timeFormat.setForeground(QColor(SERVER_MESSAGE_COLOR));
if (sender.isEmpty())
timeFormat.setFontWeight(QFont::Bold);
cursor.setCharFormat(timeFormat);
cursor.insertText(QDateTime::currentDateTime().toString("[hh:mm:ss] "));
}
// nickname
QTextCharFormat senderFormat;
if (tabSupervisor && tabSupervisor->getUserInfo() && (sender == QString::fromStdString(tabSupervisor->getUserInfo()->name()))) {
senderFormat.setForeground(QBrush(getCustomMentionColor()));
senderFormat.setFontWeight(QFont::Bold);
} else {
senderFormat.setForeground(QBrush(OTHER_USER_COLOR));
if (playerBold)
senderFormat.setFontWeight(QFont::Bold);
}
senderFormat.setAnchor(true);
senderFormat.setAnchorHref("user://" + QString::number(userLevel) + "_" + sender);
if (sameSender) {
cursor.insertText(" ");
} else {
if (!sender.isEmpty() && tabSupervisor->getUserListsTab()) {
const int pixelSize = QFontInfo(cursor.charFormat().font()).pixelSize();
QMap<QString, UserListTWI *> buddyList = tabSupervisor->getUserListsTab()->getBuddyList()->getUsers();
cursor.insertImage(UserLevelPixmapGenerator::generatePixmap(pixelSize, userLevel, buddyList.contains(sender)).toImage());
cursor.insertText(" ");
}
cursor.setCharFormat(senderFormat);
if (!sender.isEmpty())
sender.append(": ");
cursor.insertText(sender);
}
// use different color for server messages
defaultFormat = QTextCharFormat();
if (sender.isEmpty()) {
switch (messageType) {
case Event_RoomSay::Welcome:
defaultFormat.setForeground(Qt::darkGreen);
defaultFormat.setFontWeight(QFont::Bold);
break;
case Event_RoomSay::ChatHistory:
defaultFormat.setForeground(Qt::gray);
defaultFormat.setFontWeight(QFont::Light);
defaultFormat.setFontItalic(true);
break;
}
}
cursor.setCharFormat(defaultFormat);
bool mentionEnabled = settingsCache->getChatMention();
highlightedWords = settingsCache->getHighlightWords().split(' ', QString::SkipEmptyParts);
// parse the message
while (message.size())
{
QChar c = message.at(0);
switch(c.toLatin1())
{
case '[':
checkTag(cursor, message);
break;
case '@':
if(mentionEnabled) {
checkMention(cursor, message, sender, userLevel);
} else {
cursor.insertText(c, defaultFormat);
message = message.mid(1);
}
break;
case ' ':
cursor.insertText(c, defaultFormat);
message = message.mid(1);
break;
default:
if(c.isLetterOrNumber()) {
checkWord(cursor, message);
} else {
cursor.insertText(c, defaultFormat);
message = message.mid(1);
}
break;
}
}
if (atBottom)
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}
void ChatView::checkTag(QTextCursor &cursor, QString &message)
{
if (message.startsWith("[card]"))
{
message = message.mid(6);
int closeTagIndex = message.indexOf("[/card]");
QString cardName = message.left(closeTagIndex);
if (closeTagIndex == -1)
message.clear();
else
message = message.mid(closeTagIndex + 7);
appendCardTag(cursor, cardName);
return;
}
if (message.startsWith("[["))
{
message = message.mid(2);
int closeTagIndex = message.indexOf("]]");
QString cardName = message.left(closeTagIndex);
if (closeTagIndex == -1)
message.clear();
else
message = message.mid(closeTagIndex + 2);
appendCardTag(cursor, cardName);
return;
}
if (message.startsWith("[url]"))
{
message = message.mid(5);
int closeTagIndex = message.indexOf("[/url]");
QString url = message.left(closeTagIndex);
if (closeTagIndex == -1)
message.clear();
else
message = message.mid(closeTagIndex + 6);
appendUrlTag(cursor, url);
return;
}
// no valid tag found
checkWord(cursor, message);
}
void ChatView::checkMention(QTextCursor &cursor, QString &message, QString &sender, UserLevelFlags userLevel)
{
const QRegExp notALetterOrNumber = QRegExp("[^a-zA-Z0-9]");
int firstSpace = message.indexOf(' ');
QString fullMentionUpToSpaceOrEnd = (firstSpace == -1) ? message.mid(1) : message.mid(1, firstSpace - 1);
QString mentionIntact = fullMentionUpToSpaceOrEnd;
QMap<QString, UserListTWI *> userList = tabSupervisor->getUserListsTab()->getAllUsersList()->getUsers();
while (fullMentionUpToSpaceOrEnd.size())
{
if (isFullMentionAValidUser(userList, fullMentionUpToSpaceOrEnd)) // Is there a user online named this?
{
if (userName.toLower() == fullMentionUpToSpaceOrEnd.toLower()) // Is this user you?
{
// You have received a valid mention!!
soundEngine->playSound("chat_mention");
mentionFormat.setBackground(QBrush(getCustomMentionColor()));
mentionFormat.setForeground(settingsCache->getChatMentionForeground() ? QBrush(Qt::white) : QBrush(Qt::black));
cursor.insertText(mention, mentionFormat);
message = message.mid(mention.size());
QApplication::alert(this);
if (settingsCache->getShowMentionPopup() && shouldShowSystemPopup())
{
QString ref = sender.left(sender.length() - 2);
showSystemPopup(ref);
}
} else {
QString correctUserName = getNameFromUserList(userList, fullMentionUpToSpaceOrEnd);
UserListTWI *vlu = userList.value(correctUserName);
mentionFormatOtherUser.setAnchorHref("user://" + QString::number(vlu->getUserInfo().user_level()) + "_" + correctUserName);
cursor.insertText("@" + correctUserName, mentionFormatOtherUser);
message = message.mid(correctUserName.size() + 1);
}
cursor.setCharFormat(defaultFormat);
return;
}
if (isModeratorSendingGlobal(userLevel, fullMentionUpToSpaceOrEnd)) {
// Moderator Sending Global Message
soundEngine->playSound("all_mention");
mentionFormat.setBackground(QBrush(getCustomMentionColor()));
mentionFormat.setForeground(settingsCache->getChatMentionForeground() ? QBrush(Qt::white) : QBrush(Qt::black));
cursor.insertText("@" + fullMentionUpToSpaceOrEnd, mentionFormat);
message = message.mid(fullMentionUpToSpaceOrEnd.size() + 1);
QApplication::alert(this);
if (settingsCache->getShowMentionPopup() && shouldShowSystemPopup())
{
QString ref = sender.left(sender.length() - 2);
showSystemPopup(ref);
}
cursor.setCharFormat(defaultFormat);
return;
}
if (fullMentionUpToSpaceOrEnd.right(1).indexOf(notALetterOrNumber) == -1 || fullMentionUpToSpaceOrEnd.size() < 2)
{
cursor.insertText("@" + mentionIntact, defaultFormat);
message = message.mid(mentionIntact.size() + 1);
cursor.setCharFormat(defaultFormat);
return;
}
fullMentionUpToSpaceOrEnd.chop(1);
}
// no valid mention found
checkWord(cursor, message);
}
void ChatView::checkWord(QTextCursor &cursor, QString &message)
{
// extract the first word
QString rest;
QString fullWordUpToSpaceOrEnd = extractNextWord(message, rest);
// check urls
if (fullWordUpToSpaceOrEnd.startsWith("http://", Qt::CaseInsensitive) ||
fullWordUpToSpaceOrEnd.startsWith("https://", Qt::CaseInsensitive) ||
fullWordUpToSpaceOrEnd.startsWith("www.", Qt::CaseInsensitive))
{
QUrl qUrl(fullWordUpToSpaceOrEnd);
if (qUrl.isValid())
{
appendUrlTag(cursor, fullWordUpToSpaceOrEnd);
cursor.insertText(rest, defaultFormat);
return;
}
}
// check word mentions
foreach (QString word, highlightedWords)
{
if (fullWordUpToSpaceOrEnd.compare(word, Qt::CaseInsensitive) == 0)
{
// You have received a valid mention of custom word!!
highlightFormat.setBackground(QBrush(getCustomHighlightColor()));
highlightFormat.setForeground(settingsCache->getChatHighlightForeground() ? QBrush(Qt::white) : QBrush(Qt::black));
cursor.insertText(fullWordUpToSpaceOrEnd, highlightFormat);
cursor.insertText(rest, defaultFormat);
QApplication::alert(this);
return;
}
}
// not a special word; just print it
cursor.insertText(fullWordUpToSpaceOrEnd + rest, defaultFormat);
}
QString ChatView::extractNextWord(QString &message, QString &rest)
{
// get the first next space and extract the word
QString word;
int firstSpace = message.indexOf(' ');
if(firstSpace == -1)
{
word = message;
message.clear();
} else {
word = message.mid(0, firstSpace);
message = message.mid(firstSpace);
}
// remove any punctution from the end and pass it separately
for (int len = word.size() - 1; len >= 0; --len)
{
if(word.at(len).isLetterOrNumber())
{
rest = word.mid(len + 1);
return word.mid(0, len + 1);
}
}
rest = word;
return QString();
}
bool ChatView::isModeratorSendingGlobal(QFlags<ServerInfo_User::UserLevelFlag> userLevelFlag, QString message)
{
int userLevel = QString::number(userLevelFlag).toInt();
QStringList getAttentionList;
getAttentionList << "/all"; // Send a message to all users
if (getAttentionList.contains(message) && (userLevel & ServerInfo_User::IsModerator || userLevel & ServerInfo_User::IsAdmin))
return true;
return false;
}
void ChatView::actMessageClicked() {
emit messageClickedSignal();
}
bool ChatView::shouldShowSystemPopup() {
return QApplication::activeWindow() == 0 || QApplication::focusWidget() == 0 ||tabSupervisor->currentIndex() != tabSupervisor->indexOf(this);
}
void ChatView::showSystemPopup(QString &sender) {
emit showMentionPopup(sender);
}
QColor ChatView::getCustomMentionColor() {
QColor customColor;
customColor.setNamedColor("#" + settingsCache->getChatMentionColor());
return customColor.isValid() ? customColor : DEFAULT_MENTION_COLOR;
}
QColor ChatView::getCustomHighlightColor() {
QColor customColor;
customColor.setNamedColor("#" + settingsCache->getChatHighlightColor());
return customColor.isValid() ? customColor : DEFAULT_MENTION_COLOR;
}
/**
Returns the correct case version of the provided username, if no correct casing version
was found then the provided name is not available and will return an empty QString.
*/
QString ChatView::getNameFromUserList(QMap<QString, UserListTWI *> &userList, QString &userName) {
QMap<QString, UserListTWI *>::iterator i;
QString lowerUserName = userName.toLower();
for (i = userList.begin(); i != userList.end(); ++i) {
if (i.key().toLower() == lowerUserName)
return i.key();
}
return QString();
}
bool ChatView::isFullMentionAValidUser(QMap<QString, UserListTWI *> &userList, QString userNameToMatch)
{
QString userNameToMatchLower = userNameToMatch.toLower();
QMap<QString, UserListTWI *>::iterator i;
for (i = userList.begin(); i != userList.end(); ++i)
if (i.key().toLower() == userNameToMatchLower)
return true;
return false;
}
void ChatView::clearChat() {
document()->clear();
lastSender = "";
}
void ChatView::enterEvent(QEvent * /*event*/)
{
setMouseTracking(true);
}
void ChatView::leaveEvent(QEvent * /*event*/)
{
setMouseTracking(false);
}
QTextFragment ChatView::getFragmentUnderMouse(const QPoint &pos) const
{
QTextCursor cursor(cursorForPosition(pos));
QTextBlock block(cursor.block());
QTextBlock::iterator it;
for (it = block.begin(); !(it.atEnd()); ++it) {
QTextFragment frag = it.fragment();
if (frag.contains(cursor.position()))
return frag;
}
return QTextFragment();
}
void ChatView::mouseMoveEvent(QMouseEvent *event)
{
QString anchorHref = getFragmentUnderMouse(event->pos()).charFormat().anchorHref();
if (!anchorHref.isEmpty()) {
const int delimiterIndex = anchorHref.indexOf("://");
if (delimiterIndex != -1) {
const QString scheme = anchorHref.left(delimiterIndex);
hoveredContent = anchorHref.mid(delimiterIndex + 3);
if (scheme == "card") {
hoveredItemType = HoveredCard;
emit cardNameHovered(hoveredContent);
} else if (scheme == "user")
hoveredItemType = HoveredUser;
else
hoveredItemType = HoveredUrl;
viewport()->setCursor(Qt::PointingHandCursor);
} else {
hoveredItemType = HoveredNothing;
viewport()->setCursor(Qt::IBeamCursor);
}
} else {
hoveredItemType = HoveredNothing;
viewport()->setCursor(Qt::IBeamCursor);
}
QTextBrowser::mouseMoveEvent(event);
}
void ChatView::mousePressEvent(QMouseEvent *event)
{
switch (hoveredItemType) {
case HoveredCard: {
if ((event->button() == Qt::MidButton) || (event->button() == Qt::LeftButton))
emit showCardInfoPopup(event->globalPos(), hoveredContent);
break;
}
case HoveredUser: {
if (event->button() != Qt::MidButton) {
const int delimiterIndex = hoveredContent.indexOf("_");
const QString userName = hoveredContent.mid(delimiterIndex + 1);
switch(event->button()) {
case Qt::RightButton :{
UserLevelFlags userLevel(hoveredContent.left(delimiterIndex).toInt());
userContextMenu->showContextMenu(event->globalPos(), userName, userLevel);
break;
}
case Qt::LeftButton :{
if (event->modifiers() == Qt::ControlModifier) {
emit openMessageDialog(userName, true);
} else
emit addMentionTag("@" + userName);
break;
}
default:
break;
}
}
break;
}
default: {
QTextBrowser::mousePressEvent(event);
}
}
}
void ChatView::mouseReleaseEvent(QMouseEvent *event)
{
if ((event->button() == Qt::MidButton) || (event->button() == Qt::LeftButton))
emit deleteCardInfoPopup(QString("_"));
QTextBrowser::mouseReleaseEvent(event);
}
void ChatView::openLink(const QUrl &link)
{
if ((link.scheme() == "card") || (link.scheme() == "user"))
return;
QDesktopServices::openUrl(link);
}