Added patch from Anonymous Coward - readable chat

Added new options to the Chat settings window:
- Enable custom fonts
- Enable custom font size
- Enable bold
- Enable italics
- Minimum text contrast

git-svn-id: http://svn.code.sf.net/p/retroshare/code/trunk@5579 b45a01b8-16f6-495d-af2f-9b41ad6348cc
This commit is contained in:
thunder2 2012-09-21 21:23:14 +00:00
parent f336524bac
commit 28ec721c1b
6 changed files with 265 additions and 44 deletions

View File

@ -401,6 +401,25 @@ void FriendsDialog::addChatMsg(bool incoming, bool history, const QString &name,
formatTextFlag |= RSHTML_FORMATTEXT_EMBED_SMILEYS; formatTextFlag |= RSHTML_FORMATTEXT_EMBED_SMILEYS;
} }
// Always fix colors
formatTextFlag |= RSHTML_FORMATTEXT_FIX_COLORS;
qreal desiredContrast = Settings->valueFromGroup("Chat", "MinimumContrast", 4.5).toDouble();
QColor backgroundColor = ui.groupChatTab->palette().base().color();
// Remove font name, size, bold, italics?
if (!Settings->valueFromGroup("Chat", "EnableCustomFonts", true).toBool()) {
formatTextFlag |= RSHTML_FORMATTEXT_REMOVE_FONT_FAMILY;
}
if (!Settings->valueFromGroup("Chat", "EnableCustomFontSize", true).toBool()) {
formatTextFlag |= RSHTML_FORMATTEXT_REMOVE_FONT_SIZE;
}
if (!Settings->valueFromGroup("Chat", "EnableBold", true).toBool()) {
formatTextFlag |= RSHTML_FORMATTEXT_REMOVE_FONT_WEIGHT;
}
if (!Settings->valueFromGroup("Chat", "EnableItalics", true).toBool()) {
formatTextFlag |= RSHTML_FORMATTEXT_REMOVE_FONT_STYLE;
}
ChatStyle::enumFormatMessage type; ChatStyle::enumFormatMessage type;
if (incoming) { if (incoming) {
if (history) { if (history) {
@ -416,7 +435,7 @@ void FriendsDialog::addChatMsg(bool incoming, bool history, const QString &name,
} }
} }
QString formattedMessage = RsHtml().formatText(ui.msgText->document(), message, formatTextFlag); QString formattedMessage = RsHtml().formatText(ui.msgText->document(), message, formatTextFlag, backgroundColor, desiredContrast);
QString formatMsg = style.formatMessage(type, name, incoming ? recvTime : sendTime, formattedMessage); QString formatMsg = style.formatMessage(type, name, incoming ? recvTime : sendTime, formattedMessage);
ui.msgText->append(formatMsg); ui.msgText->append(formatMsg);

View File

@ -320,6 +320,25 @@ void ChatWidget::addChatMsg(bool incoming, const QString &name, const QDateTime
formatTextFlag |= RSHTML_FORMATTEXT_EMBED_SMILEYS; formatTextFlag |= RSHTML_FORMATTEXT_EMBED_SMILEYS;
} }
// Always fix colors
formatTextFlag |= RSHTML_FORMATTEXT_FIX_COLORS;
qreal desiredContrast = Settings->valueFromGroup("Chat", "MinimumContrast", 4.5).toDouble();
QColor backgroundColor = ui->textBrowser->palette().base().color();
// Remove font name, size, bold, italics?
if (!Settings->valueFromGroup("Chat", "EnableCustomFonts", true).toBool()) {
formatTextFlag |= RSHTML_FORMATTEXT_REMOVE_FONT_FAMILY;
}
if (!Settings->valueFromGroup("Chat", "EnableCustomFontSize", true).toBool()) {
formatTextFlag |= RSHTML_FORMATTEXT_REMOVE_FONT_SIZE;
}
if (!Settings->valueFromGroup("Chat", "EnableBold", true).toBool()) {
formatTextFlag |= RSHTML_FORMATTEXT_REMOVE_FONT_WEIGHT;
}
if (!Settings->valueFromGroup("Chat", "EnableItalics", true).toBool()) {
formatTextFlag |= RSHTML_FORMATTEXT_REMOVE_FONT_STYLE;
}
ChatStyle::enumFormatMessage type; ChatStyle::enumFormatMessage type;
if (chatType == TYPE_OFFLINE) { if (chatType == TYPE_OFFLINE) {
type = ChatStyle::FORMATMSG_OOUTGOING; type = ChatStyle::FORMATMSG_OOUTGOING;
@ -335,7 +354,7 @@ void ChatWidget::addChatMsg(bool incoming, const QString &name, const QDateTime
formatFlag |= CHAT_FORMATMSG_SYSTEM; formatFlag |= CHAT_FORMATMSG_SYSTEM;
} }
QString formattedMessage = RsHtml().formatText(ui->textBrowser->document(), message, formatTextFlag); QString formattedMessage = RsHtml().formatText(ui->textBrowser->document(), message, formatTextFlag, backgroundColor, desiredContrast);
QString formatMsg = chatStyle.formatMessage(type, name, incoming ? sendTime : recvTime, formattedMessage, formatFlag); QString formatMsg = chatStyle.formatMessage(type, name, incoming ? sendTime : recvTime, formattedMessage, formatFlag);
ui->textBrowser->append(formatMsg); ui->textBrowser->append(formatMsg);

View File

@ -99,8 +99,13 @@ bool
ChatPage::save(QString &/*errmsg*/) ChatPage::save(QString &/*errmsg*/)
{ {
Settings->beginGroup(QString("Chat")); Settings->beginGroup(QString("Chat"));
Settings->setValue(QString::fromUtf8("Emoteicons_PrivatChat"), ui.checkBox_emoteprivchat->isChecked()); Settings->setValue("Emoteicons_PrivatChat", ui.checkBox_emoteprivchat->isChecked());
Settings->setValue(QString::fromUtf8("Emoteicons_GroupChat"), ui.checkBox_emotegroupchat->isChecked()); Settings->setValue("Emoteicons_GroupChat", ui.checkBox_emotegroupchat->isChecked());
Settings->setValue("EnableCustomFonts", ui.checkBox_enableCustomFonts->isChecked());
Settings->setValue("EnableCustomFontSize", ui.checkBox_enableCustomFontSize->isChecked());
Settings->setValue("EnableBold", ui.checkBox_enableBold->isChecked());
Settings->setValue("EnableItalics", ui.checkBox_enableItalics->isChecked());
Settings->setValue("MinimumContrast", ui.minimumContrast->value());
Settings->endGroup(); Settings->endGroup();
Settings->setChatScreenFont(fontTempChat.toString()); Settings->setChatScreenFont(fontTempChat.toString());
@ -154,8 +159,13 @@ void
ChatPage::load() ChatPage::load()
{ {
Settings->beginGroup(QString("Chat")); Settings->beginGroup(QString("Chat"));
ui.checkBox_emoteprivchat->setChecked(Settings->value(QString::fromUtf8("Emoteicons_PrivatChat"), true).toBool()); ui.checkBox_emoteprivchat->setChecked(Settings->value("Emoteicons_PrivatChat", true).toBool());
ui.checkBox_emotegroupchat->setChecked(Settings->value(QString::fromUtf8("Emoteicons_GroupChat"), true).toBool()); ui.checkBox_emotegroupchat->setChecked(Settings->value("Emoteicons_GroupChat", true).toBool());
ui.checkBox_enableCustomFonts->setChecked(Settings->value("EnableCustomFonts", true).toBool());
ui.checkBox_enableCustomFontSize->setChecked(Settings->value("EnableCustomFontSize", true).toBool());
ui.checkBox_enableBold->setChecked(Settings->value("EnableBold", true).toBool());
ui.checkBox_enableItalics->setChecked(Settings->value("EnableItalics", true).toBool());
ui.minimumContrast->setValue(Settings->value("MinimumContrast", 4.5).toDouble());
Settings->endGroup(); Settings->endGroup();
fontTempChat.fromString(Settings->getChatScreenFont()); fontTempChat.fromString(Settings->getChatScreenFont());

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>405</width> <width>444</width>
<height>326</height> <height>378</height>
</rect> </rect>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
@ -55,6 +55,83 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="checkBox_enableCustomFonts">
<property name="text">
<string>Enable custom fonts</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_enableCustomFontSize">
<property name="text">
<string>Enable custom font size</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_enableBold">
<property name="text">
<string>Enable bold</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_enableItalics">
<property name="text">
<string>Enable italics</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_40">
<item>
<widget class="QLabel" name="label40">
<property name="text">
<string>Minimum text contrast</string>
</property>
</widget>
</item>
<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="QDoubleSpinBox" name="minimumContrast">
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>21.000000000000000</double>
</property>
<property name="singleStep">
<double>0.500000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<widget class="QCheckBox" name="sendMessageWithCtrlReturn"> <widget class="QCheckBox" name="sendMessageWithCtrlReturn">
<property name="text"> <property name="text">

View File

@ -327,7 +327,7 @@ void RsHtml::embedHtml(QTextDocument *textDocument, QDomDocument& doc, QDomEleme
} }
} }
QString RsHtml::formatText(QTextDocument *textDocument, const QString &text, ulong flag) QString RsHtml::formatText(QTextDocument *textDocument, const QString &text, ulong flag, const QColor &backgroundColor, qreal desiredContrast)
{ {
if (flag == 0 || text.isEmpty()) { if (flag == 0 || text.isEmpty()) {
// nothing to do // nothing to do
@ -353,15 +353,8 @@ QString RsHtml::formatText(QTextDocument *textDocument, const QString &text, ulo
QString formattedText = doc.toString(-1); // -1 removes any annoying carriage return misinterpreted by QTextEdit QString formattedText = doc.toString(-1); // -1 removes any annoying carriage return misinterpreted by QTextEdit
unsigned int optimizeFlag = 0; if (flag & RSHTML_OPTIMIZEHTML_MASK) {
if (flag & RSHTML_FORMATTEXT_REMOVE_FONT) { optimizeHtml(formattedText, flag & RSHTML_OPTIMIZEHTML_MASK, backgroundColor, desiredContrast);
optimizeFlag |= RSHTML_OPTIMIZEHTML_REMOVE_FONT;
}
if (flag & RSHTML_FORMATTEXT_REMOVE_COLOR) {
optimizeFlag |= RSHTML_OPTIMIZEHTML_REMOVE_COLOR;
}
if (optimizeFlag || (flag & RSHTML_FORMATTEXT_OPTIMIZE)) {
optimizeHtml(formattedText, optimizeFlag);
} }
return formattedText; return formattedText;
@ -421,7 +414,80 @@ static void removeElement(QDomElement& parentElement, QDomElement& element)
parentElement.removeChild(element); parentElement.removeChild(element);
} }
static void optimizeHtml(QDomDocument& doc, QDomElement& currentElement, unsigned int flag) static qreal linearizeColorComponent(qreal v)
{
if (v <= 0.03928) return v/12.92;
else return pow((v+0.055)/1.055, 2.4);
}
static qreal getRelativeLuminance(const QColor &c)
{
qreal r = linearizeColorComponent(c.redF()) * 0.2126;
qreal g = linearizeColorComponent(c.greenF()) * 0.7152;
qreal b = linearizeColorComponent(c.blueF()) * 0.0722;
return r+g+b;
}
/**
* @brief Compute the contrast between two relative luminances.
* See: http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G18
* @param lum1, lum2 Relative luminances returned by getRelativeLuminance().
* @return Contrast between 1 and 21.
*/
static qreal getContrastRatio(qreal lum1, qreal lum2)
{
if (lum2 > lum1) {
qreal t = lum1;
lum1 = lum2;
lum2 = t;
}
return (lum1+0.05)/(lum2+0.05);
}
/**
* @brief Find a color with the same hue that provides the desired contrast with bglum.
* @param[in,out] val Name of the original color. Will be modified.
* @param bglum Background's relative luminance as returned by getRelativeLuminance().
*/
static void findBestColor(QString &val, qreal bglum, qreal desiredContrast)
{
// Change text color to get a good contrast with the background
QColor c(val);
qreal lum = ::getRelativeLuminance(c);
// Keep text color darker/brighter than the bg if possible
qreal lowContrast = ::getContrastRatio(bglum, 0.0);
qreal highContrast = ::getContrastRatio(bglum, 1.0);
bool searchDown = (lum <= bglum && lowContrast >= desiredContrast)
|| (lum > bglum && highContrast < desiredContrast && lowContrast >= highContrast);
// There's no such thing as too much contrast on a bright background,
// but on a dark background it creates haloing which makes text hard to read.
// So we enforce desired contrast when the bg is dark.
if (!searchDown || ::getContrastRatio(lum, bglum) < desiredContrast) {
// Bisection search of the correct "lightness" to get the desired contrast
qreal minl = searchDown ? 0.0 : bglum;
qreal maxl = searchDown ? bglum : 1.0;
do {
QColor d = c;
qreal midl = (minl+maxl)/2.0;
d.setHslF(c.hslHueF(), c.hslSaturationF(), midl);
qreal lum = ::getRelativeLuminance(d);
if ((::getContrastRatio(lum, bglum) < desiredContrast) ^ searchDown ) {
minl = midl;
}
else {
maxl = midl;
}
} while (maxl - minl > 0.01);
c.setHslF(c.hslHueF(), c.hslSaturationF(), minl);
val = c.name();
}
}
static void optimizeHtml(QDomDocument& doc, QDomElement& currentElement, unsigned int flag, qreal bglum, qreal desiredContrast)
{ {
if (currentElement.tagName().toLower() == "html") { if (currentElement.tagName().toLower() == "html") {
// change <html> to <span> // change <html> to <span>
@ -443,7 +509,7 @@ static void optimizeHtml(QDomDocument& doc, QDomElement& currentElement, unsigne
style.replace("margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;", "margin:0px 0px 0px 0px;"); style.replace("margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;", "margin:0px 0px 0px 0px;");
style.replace("; ", ";"); style.replace("; ", ";");
if (flag & (RSHTML_OPTIMIZEHTML_REMOVE_FONT | RSHTML_OPTIMIZEHTML_REMOVE_COLOR)) { if (flag) {
QStringList styles = style.split(';'); QStringList styles = style.split(';');
style.clear(); style.clear();
foreach (QString pair, styles) { foreach (QString pair, styles) {
@ -451,21 +517,35 @@ static void optimizeHtml(QDomDocument& doc, QDomElement& currentElement, unsigne
QStringList keyvalue = pair.split(':'); QStringList keyvalue = pair.split(':');
if (keyvalue.length() == 2) { if (keyvalue.length() == 2) {
QString key = keyvalue.at(0).trimmed(); QString key = keyvalue.at(0).trimmed();
QString val = keyvalue.at(1).trimmed();
if (flag & RSHTML_OPTIMIZEHTML_REMOVE_FONT) { if ((flag & RSHTML_FORMATTEXT_REMOVE_FONT_FAMILY && key == "font-family") ||
if (key == "font-family" || (flag & RSHTML_FORMATTEXT_REMOVE_FONT_SIZE && key == "font-size") ||
key == "font-size" || (flag & RSHTML_FORMATTEXT_REMOVE_FONT_WEIGHT && key == "font-weight") ||
key == "font-weight" || (flag & RSHTML_FORMATTEXT_REMOVE_FONT_STYLE && key == "font-style")) {
key == "font-style") { continue;
continue;
}
} }
if (flag & RSHTML_OPTIMIZEHTML_REMOVE_COLOR) { if (flag & RSHTML_FORMATTEXT_REMOVE_COLOR) {
if (key == "color") { if (key == "color") {
continue; continue;
} }
} }
style += key + ":" + keyvalue.at(1).trimmed() + ";"; else if (flag & RSHTML_FORMATTEXT_FIX_COLORS) {
if (key == "color") {
findBestColor(val, bglum, desiredContrast);
}
}
if (flag & (RSHTML_FORMATTEXT_REMOVE_COLOR | RSHTML_FORMATTEXT_FIX_COLORS)) {
if (key == "background" || key == "background-color") {
// Remove background color because if we change the text color,
// it can become unreadable on the original background.
// Also, FIX_COLORS is intended to display text on the default
// background color of the operating system.
continue;
}
}
style += key + ":" + val + ";";
} else { } else {
style += pair + ";"; style += pair + ";";
} }
@ -513,7 +593,7 @@ static void optimizeHtml(QDomDocument& doc, QDomElement& currentElement, unsigne
} }
// iterate children // iterate children
optimizeHtml(doc, element, flag); optimizeHtml(doc, element, flag, bglum, desiredContrast);
// <p> // <p>
if (element.tagName().toLower() == "p") { if (element.tagName().toLower() == "p") {
@ -598,7 +678,19 @@ void RsHtml::optimizeHtml(QTextEdit *textEdit, QString &text, unsigned int flag)
optimizeHtml(text, flag); optimizeHtml(text, flag);
} }
void RsHtml::optimizeHtml(QString &text, unsigned int flag) /**
* @brief Make an HTML document smaller by removing useless stuff.
* Can also change the text color to make it more readable.
* @param[in,out] text HTML document.
* @param flag Bitfield of RSHTML_FORMATTEXT_* constants (they must be part of
* RSHTML_OPTIMIZEHTML_MASK).
* @param backgroundColor Background color of the widget where the text will be
* displayed. Needed only if RSHTML_FORMATTEXT_FIX_COLORS
* is passed inside flag.
* @param desiredContrast Minimum contrast between text and background color,
* between 1 and 21.
*/
void RsHtml::optimizeHtml(QString &text, unsigned int flag, const QColor &backgroundColor, qreal desiredContrast)
{ {
int originalLength = text.length(); int originalLength = text.length();
@ -611,7 +703,7 @@ void RsHtml::optimizeHtml(QString &text, unsigned int flag)
} }
QDomElement body = doc.documentElement(); QDomElement body = doc.documentElement();
::optimizeHtml(doc, body, flag); ::optimizeHtml(doc, body, flag, ::getRelativeLuminance(backgroundColor), desiredContrast);
text = doc.toString(-1); text = doc.toString(-1);
std::cerr << "Optimized text to " << text.length() << " bytes , instead of " << originalLength << std::endl; std::cerr << "Optimized text to " << text.length() << " bytes , instead of " << originalLength << std::endl;

View File

@ -31,17 +31,21 @@
#include <QRegExp> #include <QRegExp>
/* Flags for RsHtml::formatText */ /* Flags for RsHtml::formatText */
#define RSHTML_FORMATTEXT_EMBED_SMILEYS 1 #define RSHTML_FORMATTEXT_EMBED_SMILEYS 1
#define RSHTML_FORMATTEXT_EMBED_LINKS 2 #define RSHTML_FORMATTEXT_EMBED_LINKS 2
#define RSHTML_FORMATTEXT_REMOVE_FONT 4 #define RSHTML_FORMATTEXT_OPTIMIZE 4
#define RSHTML_FORMATTEXT_REMOVE_COLOR 8 #define RSHTML_FORMATTEXT_REPLACE_LINKS 8
#define RSHTML_FORMATTEXT_CLEANSTYLE (RSHTML_FORMATTEXT_REMOVE_FONT | RSHTML_FORMATTEXT_REMOVE_COLOR) #define RSHTML_FORMATTEXT_REMOVE_COLOR 16
#define RSHTML_FORMATTEXT_OPTIMIZE 16 #define RSHTML_FORMATTEXT_FIX_COLORS 32 /* Make text readable */
#define RSHTML_FORMATTEXT_REPLACE_LINKS 32 #define RSHTML_FORMATTEXT_REMOVE_FONT_WEIGHT 64 /* Remove bold */
#define RSHTML_FORMATTEXT_REMOVE_FONT_STYLE 128 /* Remove italics */
#define RSHTML_FORMATTEXT_REMOVE_FONT_FAMILY 256
#define RSHTML_FORMATTEXT_REMOVE_FONT_SIZE 512
#define RSHTML_FORMATTEXT_REMOVE_FONT (RSHTML_FORMATTEXT_REMOVE_FONT_WEIGHT | RSHTML_FORMATTEXT_REMOVE_FONT_STYLE | RSHTML_FORMATTEXT_REMOVE_FONT_FAMILY | RSHTML_FORMATTEXT_REMOVE_FONT_SIZE)
#define RSHTML_FORMATTEXT_CLEANSTYLE (RSHTML_FORMATTEXT_REMOVE_FONT | RSHTML_FORMATTEXT_REMOVE_COLOR)
/* Flags for RsHtml::formatText */ /* Flags for RsHtml::optimizeHtml */
#define RSHTML_OPTIMIZEHTML_REMOVE_FONT 2 #define RSHTML_OPTIMIZEHTML_MASK (RSHTML_FORMATTEXT_CLEANSTYLE | RSHTML_FORMATTEXT_FIX_COLORS)
#define RSHTML_OPTIMIZEHTML_REMOVE_COLOR 1
class QTextEdit; class QTextEdit;
class QTextDocument; class QTextDocument;
@ -57,11 +61,11 @@ public:
static void initEmoticons(const QHash< QString, QString >& hash); static void initEmoticons(const QHash< QString, QString >& hash);
QString formatText(QTextDocument *textDocument, const QString &text, ulong flag); QString formatText(QTextDocument *textDocument, const QString &text, ulong flag, const QColor &backgroundColor = Qt::white, qreal desiredContrast = 1.0);
static bool findAnchors(const QString &text, QStringList& urls); static bool findAnchors(const QString &text, QStringList& urls);
static void optimizeHtml(QTextEdit *textEdit, QString &text, unsigned int flag = 0); static void optimizeHtml(QTextEdit *textEdit, QString &text, unsigned int flag = 0);
static void optimizeHtml(QString &text, unsigned int flag = 0); static void optimizeHtml(QString &text, unsigned int flag = 0, const QColor &backgroundColor = Qt::white, qreal desiredContrast = 1.0);
static QString toHtml(QString text, bool realHtml = true); static QString toHtml(QString text, bool realHtml = true);
static bool makeEmbeddedImage(const QString &fileName, QString &embeddedImage, const int maxPixels); static bool makeEmbeddedImage(const QString &fileName, QString &embeddedImage, const int maxPixels);