servatrice/cockatrice/src/chatview.cpp
2015-07-04 18:26:54 -04:00

523 lines
19 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"
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) {
QTextBlockFormat blockFormat;
if ((evenNumber = !evenNumber))
blockFormat.setBackground(palette().alternateBase());
blockFormat.setBottomMargin(4);
cursor.insertBlock(blockFormat);
} else
cursor.insertHtml("<br>");
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 atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum();
prepareBlock().insertHtml("<font color=" + SERVER_MESSAGE_COLOR + ">" + html + "</font>");
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, QString sender, UserLevelFlags userLevel, bool playerBold)
{
bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum();
bool sameSender = (sender == lastSender) && !lastSender.isEmpty();
QTextCursor cursor = prepareBlock(sameSender);
lastSender = sender;
if (showTimestamps && !sameSender) {
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] "));
}
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) {
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);
} else
cursor.insertText(" ");
QTextCharFormat messageFormat;
if (sender.isEmpty()) {
messageFormat.setForeground(Qt::darkGreen);
messageFormat.setFontWeight(QFont::Bold);
}
cursor.setCharFormat(messageFormat);
int index = -1, bracketFirstIndex = -1, mentionFirstIndex = -1, urlFirstIndex = -1;
bool mentionEnabled = settingsCache->getChatMention();
const QRegExp urlStarter = QRegExp("https?://|\\bwww\\.");
const QRegExp phraseEnder = QRegExp("\\s");
const QRegExp notALetterOrNumber = QRegExp("[^a-zA-Z0-9]");
while (message.size())
{
// search for the first [ or @
bracketFirstIndex = message.indexOf('[');
mentionFirstIndex = mentionEnabled ? message.indexOf('@') : -1;
urlFirstIndex = message.indexOf(urlStarter);
bool startsWithBracket = (bracketFirstIndex != -1);
bool startsWithAtSymbol = (mentionFirstIndex != -1);
bool startsWithUrl = (urlFirstIndex != -1);
if (!startsWithBracket)
{
if (!startsWithAtSymbol)
{
if (!startsWithUrl)
{
// No brackets, mentions, or urls. Send message as normal
cursor.insertText(message);
break;
}
else
{
// There's a URL, lets begin!
index = urlFirstIndex;
}
}
else
{
if (!startsWithUrl)
{
// There's an @ symbol, lets begin!
index = mentionFirstIndex;
}
else
{
// There's both an @ symbol and URL, pick the first one... lets begin!
index = std::min(urlFirstIndex, mentionFirstIndex);
}
}
}
else
{
if (!startsWithAtSymbol)
{
// There's a [, look down!
index = bracketFirstIndex;
}
else
{
// There's both a [ and @, pick the first one... look down!
index = std::min(bracketFirstIndex, mentionFirstIndex);
}
if (startsWithUrl)
{
// If there's a URL, pick the first one... then lets begin!
// Otherwise, just "lets begin!"
index = std::min(index, urlFirstIndex);
}
}
if (index > 0)
{
cursor.insertText(message.left(index), defaultFormat);
message = message.mid(index);
}
if (index == bracketFirstIndex) // The message now starts with a bracket ->> [ <<- that symbol
{
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);
}
else 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);
}
else 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);
}
else
{
// Not a valid tag
cursor.insertText("[", defaultFormat);
message = message.mid(1);
}
}
else if (index == urlFirstIndex) // The message now starts with either: www. , http:// , or https://
{
int urlEndIndex = message.indexOf(phraseEnder, 0);
if (urlEndIndex == -1)
urlEndIndex = message.size();
QString urlText = message.left(urlEndIndex);
QUrl qUrl(urlText);
if (qUrl.isValid())
appendUrlTag(cursor, urlText);
else
cursor.insertText(urlText);
if (urlEndIndex == -1)
message.clear();
else
message = message.mid(urlEndIndex);
}
else if (index == mentionFirstIndex)
{
if (tabSupervisor->getIsLocalGame())
{
cursor.insertText("@");
message = message.mid(1);
}
else
{
QMap<QString, UserListTWI *> userList = tabSupervisor->getUserListsTab()->getAllUsersList()->getUsers();
int firstSpace = message.indexOf(" ");
QString fullMentionUpToSpaceOrEnd = (firstSpace == -1) ? message.mid(1) : message.mid(1, firstSpace - 1);
QString mentionIntact = fullMentionUpToSpaceOrEnd;
do
{
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!!
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);
break;
}
else if (fullMentionUpToSpaceOrEnd.right(1).indexOf(notALetterOrNumber) == -1 || fullMentionUpToSpaceOrEnd.size() < 2)
{
cursor.insertText("@" + mentionIntact, defaultFormat);
message = message.mid(mentionIntact.size() + 1);
cursor.setCharFormat(defaultFormat);
break;
}
else
{
fullMentionUpToSpaceOrEnd.chop(1);
}
}
while (fullMentionUpToSpaceOrEnd.size());
}
}
else
{
message = message.mid(1); // Not certain when this would ever be reached, but just incase lets skip the character
}
}
if (atBottom)
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}
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;
}
/**
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);
}