mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
d4ac13dbf4
Revert needed, since otherwise there is no way to do automatic sorting of includes. Also reverted change to the docs, as leaving it would make incorrect docs. In case of conflicts, includes were sorted according to the coding standards from #3839. This reverts commitb4a9f04f92
. This reverts commit5921122960
.
337 lines
13 KiB
C++
337 lines
13 KiB
C++
/*
|
||
Copyright © 2014-2015 by The qTox Project Contributors
|
||
|
||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||
|
||
qTox is libre 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 3 of the License, or
|
||
(at your option) any later version.
|
||
|
||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include "chatmessage.h"
|
||
#include "chatlinecontentproxy.h"
|
||
#include "content/text.h"
|
||
#include "content/timestamp.h"
|
||
#include "content/spinner.h"
|
||
#include "content/filetransferwidget.h"
|
||
#include "content/image.h"
|
||
#include "content/notificationicon.h"
|
||
|
||
#include <QDebug>
|
||
|
||
#include "src/persistence/settings.h"
|
||
#include "src/persistence/smileypack.h"
|
||
|
||
#define NAME_COL_WIDTH 90.0
|
||
#define TIME_COL_WIDTH 90.0
|
||
|
||
ChatMessage::ChatMessage()
|
||
{
|
||
|
||
}
|
||
|
||
ChatMessage::Ptr ChatMessage::createChatMessage(const QString &sender, const QString &rawMessage, MessageType type, bool isMe, const QDateTime &date)
|
||
{
|
||
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage);
|
||
|
||
QString text = rawMessage.toHtmlEscaped();
|
||
QString senderText = sender;
|
||
|
||
const QColor actionColor = QColor("#1818FF"); // has to match the color in innerStyle.css (div.action)
|
||
|
||
//smileys
|
||
if (Settings::getInstance().getUseEmoticons())
|
||
text = SmileyPack::getInstance().smileyfied(text);
|
||
|
||
//quotes (green text)
|
||
text = detectQuotes(detectAnchors(text), type);
|
||
|
||
//text styling
|
||
if (Settings::getInstance().getStylePreference() != Settings::StyleType::NONE)
|
||
text = detectStyle(text);
|
||
|
||
switch(type)
|
||
{
|
||
case NORMAL:
|
||
text = wrapDiv(text, "msg");
|
||
break;
|
||
case ACTION:
|
||
senderText = "*";
|
||
text = wrapDiv(QString("%1 %2").arg(sender.toHtmlEscaped(), text), "action");
|
||
msg->setAsAction();
|
||
break;
|
||
case ALERT:
|
||
text = wrapDiv(text, "alert");
|
||
break;
|
||
}
|
||
|
||
// Note: Eliding cannot be enabled for RichText items. (QTBUG-17207)
|
||
QFont baseFont = Settings::getInstance().getChatMessageFont();
|
||
QFont authorFont = baseFont;
|
||
if (isMe)
|
||
authorFont.setBold(true);
|
||
|
||
msg->addColumn(new Text(senderText, authorFont, true, sender, type == ACTION ? actionColor : Qt::black), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||
msg->addColumn(new Text(text, baseFont, false, ((type == ACTION) && isMe) ? QString("%1 %2").arg(sender, rawMessage) : rawMessage), ColumnFormat(1.0, ColumnFormat::VariableSize));
|
||
msg->addColumn(new Spinner(":/ui/chatArea/spinner.svg", QSize(16, 16), 360.0/1.6), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||
|
||
if (!date.isNull())
|
||
msg->markAsSent(date);
|
||
|
||
return msg;
|
||
}
|
||
|
||
ChatMessage::Ptr ChatMessage::createChatInfoMessage(const QString &rawMessage, SystemMessageType type, const QDateTime &date)
|
||
{
|
||
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage);
|
||
QString text = rawMessage.toHtmlEscaped();
|
||
|
||
QString img;
|
||
switch(type)
|
||
{
|
||
case INFO: img = ":/ui/chatArea/info.svg"; break;
|
||
case ERROR: img = ":/ui/chatArea/error.svg"; break;
|
||
case TYPING: img = ":/ui/chatArea/typing.svg"; break;
|
||
}
|
||
|
||
QFont baseFont = Settings::getInstance().getChatMessageFont();
|
||
|
||
msg->addColumn(new Image(QSize(18, 18), img), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||
msg->addColumn(new Text("<b>" + text + "</b>", baseFont, false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left));
|
||
msg->addColumn(new Timestamp(date, Settings::getInstance().getTimestampFormat(), baseFont), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||
|
||
return msg;
|
||
}
|
||
|
||
ChatMessage::Ptr ChatMessage::createFileTransferMessage(const QString& sender, ToxFile file, bool isMe, const QDateTime& date)
|
||
{
|
||
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage);
|
||
|
||
QFont baseFont = Settings::getInstance().getChatMessageFont();
|
||
QFont authorFont = baseFont;
|
||
if (isMe)
|
||
authorFont.setBold(true);
|
||
|
||
msg->addColumn(new Text(sender, authorFont, true), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||
msg->addColumn(new ChatLineContentProxy(new FileTransferWidget(0, file), 320, 0.6f), ColumnFormat(1.0, ColumnFormat::VariableSize));
|
||
msg->addColumn(new Timestamp(date, Settings::getInstance().getTimestampFormat(), baseFont), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||
|
||
return msg;
|
||
}
|
||
|
||
ChatMessage::Ptr ChatMessage::createTypingNotification()
|
||
{
|
||
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage);
|
||
|
||
QFont baseFont = Settings::getInstance().getChatMessageFont();
|
||
|
||
// Note: "[user]..." is just a placeholder. The actual text is set in ChatForm::setFriendTyping()
|
||
//
|
||
// FIXME: Due to circumstances, placeholder is being used in a case where
|
||
// user received typing notifications constantly since contact came online.
|
||
// This causes "[user]..." to be displayed in place of user nick, as long
|
||
// as user will keep typing. Issue #1280
|
||
msg->addColumn(new NotificationIcon(QSize(18, 18)), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||
msg->addColumn(new Text("[user]...", baseFont, false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left));
|
||
|
||
return msg;
|
||
}
|
||
|
||
ChatMessage::Ptr ChatMessage::createBusyNotification()
|
||
{
|
||
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage);
|
||
QFont baseFont = Settings::getInstance().getChatMessageFont();
|
||
baseFont.setPixelSize(baseFont.pixelSize() + 2);
|
||
baseFont.setBold(true);
|
||
|
||
msg->addColumn(new Text(QObject::tr("Resizing"), baseFont, false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Center));
|
||
|
||
return msg;
|
||
}
|
||
|
||
void ChatMessage::markAsSent(const QDateTime &time)
|
||
{
|
||
QFont baseFont = Settings::getInstance().getChatMessageFont();
|
||
|
||
// remove the spinner and replace it by $time
|
||
replaceContent(2, new Timestamp(time, Settings::getInstance().getTimestampFormat(), baseFont));
|
||
}
|
||
|
||
QString ChatMessage::toString() const
|
||
{
|
||
ChatLineContent* c = getContent(1);
|
||
if (c)
|
||
return c->getText();
|
||
|
||
return QString();
|
||
}
|
||
|
||
bool ChatMessage::isAction() const
|
||
{
|
||
return action;
|
||
}
|
||
|
||
void ChatMessage::setAsAction()
|
||
{
|
||
action = true;
|
||
}
|
||
|
||
void ChatMessage::hideSender()
|
||
{
|
||
ChatLineContent* c = getContent(0);
|
||
if (c)
|
||
c->hide();
|
||
}
|
||
|
||
void ChatMessage::hideDate()
|
||
{
|
||
ChatLineContent* c = getContent(2);
|
||
if (c)
|
||
c->hide();
|
||
}
|
||
|
||
QString ChatMessage::detectStyle(const QString &str)
|
||
{
|
||
QString out = str;
|
||
|
||
// Create regex for text styling syntax
|
||
QRegExp exp("(\\*)([^\\*]{2,})(\\*)" // Bold *text*
|
||
"|(\\*\\*)([^\\*\\*]{2,})(\\*\\*)" // Bold **text**
|
||
"|(\\/)([^\\/]{2,})(\\/)" // Italics /text/
|
||
"|(\\/\\/)([^\\/\\/]{2,})(\\/\\/)" // Italics //text//
|
||
"|(\\_)([^\\_]{2,})(\\_)" // Underline _text_
|
||
"|(\\_\\_)([^\\_\\_]{2,})(\\_\\_)" // Underline __text__
|
||
"|(\\~)([^\\~]{2,})(\\~)" // Strike ~text~
|
||
"|(\\~\\~)([^\\~\\~]{2,})(\\~\\~)" // Strike ~~text~~
|
||
"|(\\`)([^\\`]{2,})(\\`)" // Codeblock `text`
|
||
"|(\\`\\`\\`)([^\\`\\`\\`]{2,})(\\`\\`\\`)" // Codeblock ```\ntext\n```
|
||
);
|
||
|
||
int offset = 0;
|
||
while ((offset = exp.indexIn(out, offset)) != -1)
|
||
{
|
||
QString snipCheck = out.mid(offset-1,exp.cap(0).length()+2);
|
||
QString snippet = exp.cap(0).trimmed();
|
||
QString htmledSnippet;
|
||
|
||
// Only parse if surrounded by spaces, newline(s) and/or beginning/end of line
|
||
if ((snipCheck.startsWith(' ') || snipCheck.startsWith('>') || offset == 0)
|
||
&& ((snipCheck.endsWith(' ') || snipCheck.endsWith('<')) || offset + snippet.length() == out.length()))
|
||
{
|
||
int mul = 0; // Determines how many characters to strip from text
|
||
// Set mul depending on styleownPreference
|
||
if (Settings::getInstance().getStylePreference() == Settings::StyleType::WITHOUT_CHARS)
|
||
mul = 2;
|
||
|
||
// Match captured string to corresponding style format
|
||
if (exp.cap(1) == "*" && snippet.length() > 2) // Bold *text*
|
||
htmledSnippet = QString("<b>%1</b>").arg(snippet.mid(mul/2,snippet.length()-mul));
|
||
else if (exp.cap(4) == "**" && snippet.length() > 4) // Bold **text**
|
||
htmledSnippet = QString("<b>%1</b>").arg(snippet.mid(mul,snippet.length()-2*mul));
|
||
else if (exp.cap(7) == "/" && snippet.length() > 2) // Italics /text/
|
||
htmledSnippet = QString("<i>%1</i>").arg(snippet.mid(mul/2,snippet.length()-mul));
|
||
else if (exp.cap(10) == "//" && snippet.length() > 4) // Italics //text//
|
||
htmledSnippet = QString("<i>%1</i>").arg(snippet.mid(mul,snippet.length()-2*mul));
|
||
else if (exp.cap(13) == "_"&& snippet.length() > 2) // Underline _text_
|
||
htmledSnippet = QString("<u>%1</u>").arg(snippet.mid(mul/2,snippet.length()-mul));
|
||
else if (exp.cap(16) == "__" && snippet.length() > 4) // Underline __text__
|
||
htmledSnippet = QString("<u>%1</u>").arg(snippet.mid(mul,snippet.length()-2*mul));
|
||
else if (exp.cap(19) == "~" && snippet.length() > 2) // Strike ~text~
|
||
htmledSnippet = QString("<s>%1</s>").arg(snippet.mid(mul/2,snippet.length()-mul));
|
||
else if (exp.cap(22) == "~~" && snippet.length() > 4) // Strike ~~text~~
|
||
htmledSnippet = QString("<s>%1</s>").arg(snippet.mid(mul,snippet.length()-2*mul));
|
||
else if (exp.cap(25) == "`" && snippet.length() > 2) // Codeblock `text`
|
||
htmledSnippet = QString("<font color=#595959><code>%1</code></font>").arg(snippet.mid(mul/2,snippet.length()-mul));
|
||
else if (exp.cap(28) == "```" && snippet.length() > 6) // Codeblock ```text```
|
||
htmledSnippet = QString("<font color=#595959><code>%1</code></font>").arg(snippet.mid(4*mul,snippet.length()-8*mul));
|
||
else
|
||
htmledSnippet = snippet;
|
||
out.replace(offset, exp.cap().length(), htmledSnippet);
|
||
offset += htmledSnippet.length();
|
||
} else
|
||
offset += snippet.length();
|
||
}
|
||
|
||
return out;
|
||
}
|
||
|
||
QString ChatMessage::detectAnchors(const QString &str)
|
||
{
|
||
QString out = str;
|
||
|
||
// detect URIs
|
||
QRegExp exp("("
|
||
"(?:\\b)((www\\.)|(http[s]?|ftp)://)" // (protocol)://(printable - non-special character)
|
||
// http://ONEORMOREALHPA-DIGIT
|
||
"\\w+\\S+)" // any other character, lets domains and other
|
||
// ↓ link to a file, or samba share
|
||
// https://en.wikipedia.org/wiki/File_URI_scheme
|
||
"|(?:\\b)((file|smb)://)([\\S| ]*)"
|
||
"|(?:\\b)(tox:[a-zA-Z\\d]{76})" //link with full user address
|
||
"|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)" //@mail link
|
||
"|(?:\\b)(tox:\\S+@\\S+)"); // starts with `tox` then : and only alpha-digits till the end
|
||
// also accepts tox:agilob@net as simplified TOX ID
|
||
|
||
int offset = 0;
|
||
while ((offset = exp.indexIn(out, offset)) != -1)
|
||
{
|
||
QString url = exp.cap();
|
||
// If there's a trailing " it's a HTML attribute, e.g. a smiley img's title=":tox:"
|
||
if (url == "tox:\"")
|
||
{
|
||
offset += url.length();
|
||
continue;
|
||
}
|
||
QString htmledUrl;
|
||
// add scheme if not specified
|
||
if (exp.cap(2) == "www.")
|
||
htmledUrl = QString("<a href=\"http://%1\">%1</a>").arg(url);
|
||
else
|
||
htmledUrl = QString("<a href=\"%1\">%1</a>").arg(url);
|
||
out.replace(offset, exp.cap().length(), htmledUrl);
|
||
offset += htmledUrl.length();
|
||
}
|
||
|
||
return out;
|
||
}
|
||
|
||
QString ChatMessage::detectQuotes(const QString& str, MessageType type)
|
||
{
|
||
// detect text quotes
|
||
QStringList messageLines = str.split("\n");
|
||
QString quotedText;
|
||
for (int i = 0; i < messageLines.size(); ++i)
|
||
{
|
||
// don't quote first line in action message. This makes co-existence of
|
||
// quotes and action messages possible, since only first line can cause
|
||
// problems in case where there is quote in it used.
|
||
if (QRegExp("^(>|>).*").exactMatch(messageLines[i])) {
|
||
if (i > 0 || type != ACTION)
|
||
quotedText += "<span class=quote>" + messageLines[i] + "</span>";
|
||
else
|
||
quotedText += messageLines[i];
|
||
} else {
|
||
quotedText += messageLines[i];
|
||
}
|
||
|
||
if (i < messageLines.size() - 1)
|
||
quotedText += "<br/>";
|
||
}
|
||
|
||
return quotedText;
|
||
}
|
||
|
||
QString ChatMessage::wrapDiv(const QString &str, const QString &div)
|
||
{
|
||
return QString("<p class=%1>%2</p>").arg(div, /*QChar(0x200E) + */QString(str));
|
||
}
|