diff options
Diffstat (limited to 'src/Utils.cpp')
-rw-r--r-- | src/Utils.cpp | 1085 |
1 files changed, 538 insertions, 547 deletions
diff --git a/src/Utils.cpp b/src/Utils.cpp index 3f524c6c..b0fb01b1 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -38,155 +38,154 @@ template<class T, class Event> static DescInfo createDescriptionInfo(const Event &event, const QString &localUser, const QString &displayName) { - const auto msg = std::get<T>(event); - const auto sender = QString::fromStdString(msg.sender); + const auto msg = std::get<T>(event); + const auto sender = QString::fromStdString(msg.sender); - const auto username = displayName; - const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts); - auto body = utils::event_body(event).trimmed(); - if (mtx::accessors::relations(event).reply_to()) - body = QString::fromStdString(utils::stripReplyFromBody(body.toStdString())); + const auto username = displayName; + const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts); + auto body = utils::event_body(event).trimmed(); + if (mtx::accessors::relations(event).reply_to()) + body = QString::fromStdString(utils::stripReplyFromBody(body.toStdString())); - return DescInfo{QString::fromStdString(msg.event_id), - sender, - utils::messageDescription<T>(username, body, sender == localUser), - utils::descriptiveTime(ts), - msg.origin_server_ts, - ts}; + return DescInfo{QString::fromStdString(msg.event_id), + sender, + utils::messageDescription<T>(username, body, sender == localUser), + utils::descriptiveTime(ts), + msg.origin_server_ts, + ts}; } std::string utils::stripReplyFromBody(const std::string &bodyi) { - QString body = QString::fromStdString(bodyi); - QRegularExpression plainQuote("^>.*?$\n?", QRegularExpression::MultilineOption); - while (body.startsWith(">")) - body.remove(plainQuote); - if (body.startsWith("\n")) - body.remove(0, 1); + QString body = QString::fromStdString(bodyi); + QRegularExpression plainQuote("^>.*?$\n?", QRegularExpression::MultilineOption); + while (body.startsWith(">")) + body.remove(plainQuote); + if (body.startsWith("\n")) + body.remove(0, 1); - body.replace("@room", QString::fromUtf8("@\u2060room")); - return body.toStdString(); + body.replace("@room", QString::fromUtf8("@\u2060room")); + return body.toStdString(); } std::string utils::stripReplyFromFormattedBody(const std::string &formatted_bodyi) { - QString formatted_body = QString::fromStdString(formatted_bodyi); - formatted_body.remove(QRegularExpression("<mx-reply>.*</mx-reply>", - QRegularExpression::DotMatchesEverythingOption)); - formatted_body.replace("@room", QString::fromUtf8("@\u2060room")); - return formatted_body.toStdString(); + QString formatted_body = QString::fromStdString(formatted_bodyi); + formatted_body.remove(QRegularExpression("<mx-reply>.*</mx-reply>", + QRegularExpression::DotMatchesEverythingOption)); + formatted_body.replace("@room", QString::fromUtf8("@\u2060room")); + return formatted_body.toStdString(); } RelatedInfo utils::stripReplyFallbacks(const TimelineEvent &event, std::string id, QString room_id_) { - RelatedInfo related = {}; - related.quoted_user = QString::fromStdString(mtx::accessors::sender(event)); - related.related_event = std::move(id); - related.type = mtx::accessors::msg_type(event); + RelatedInfo related = {}; + related.quoted_user = QString::fromStdString(mtx::accessors::sender(event)); + related.related_event = std::move(id); + related.type = mtx::accessors::msg_type(event); - // get body, strip reply fallback, then transform the event to text, if it is a media event - // etc - related.quoted_body = QString::fromStdString(mtx::accessors::body(event)); - related.quoted_body = - QString::fromStdString(stripReplyFromBody(related.quoted_body.toStdString())); - related.quoted_body = utils::getQuoteBody(related); + // get body, strip reply fallback, then transform the event to text, if it is a media event + // etc + related.quoted_body = QString::fromStdString(mtx::accessors::body(event)); + related.quoted_body = + QString::fromStdString(stripReplyFromBody(related.quoted_body.toStdString())); + related.quoted_body = utils::getQuoteBody(related); - // get quoted body and strip reply fallback - related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event); - related.quoted_formatted_body = QString::fromStdString( - stripReplyFromFormattedBody(related.quoted_formatted_body.toStdString())); - related.room = room_id_; + // get quoted body and strip reply fallback + related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event); + related.quoted_formatted_body = QString::fromStdString( + stripReplyFromFormattedBody(related.quoted_formatted_body.toStdString())); + related.room = room_id_; - return related; + return related; } QString utils::localUser() { - return QString::fromStdString(http::client()->user_id().to_string()); + return QString::fromStdString(http::client()->user_id().to_string()); } bool utils::codepointIsEmoji(uint code) { - // TODO: Be more precise here. - return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x2b00 && code <= 0x2bff) || - (code >= 0x1f000 && code <= 0x1faff) || code == 0x200d || code == 0xfe0f; + // TODO: Be more precise here. + return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x2b00 && code <= 0x2bff) || + (code >= 0x1f000 && code <= 0x1faff) || code == 0x200d || code == 0xfe0f; } QString utils::replaceEmoji(const QString &body) { - QString fmtBody; - fmtBody.reserve(body.size()); - - QVector<uint> utf32_string = body.toUcs4(); - - bool insideFontBlock = false; - for (auto &code : utf32_string) { - if (utils::codepointIsEmoji(code)) { - if (!insideFontBlock) { - fmtBody += QStringLiteral("<font face=\"") % - UserSettings::instance()->emojiFont() % - QStringLiteral("\">"); - insideFontBlock = true; - } - - } else { - if (insideFontBlock) { - fmtBody += QStringLiteral("</font>"); - insideFontBlock = false; - } - } - if (QChar::requiresSurrogates(code)) { - QChar emoji[] = {static_cast<ushort>(QChar::highSurrogate(code)), - static_cast<ushort>(QChar::lowSurrogate(code))}; - fmtBody.append(emoji, 2); - } else { - fmtBody.append(QChar(static_cast<ushort>(code))); - } - } - if (insideFontBlock) { + QString fmtBody; + fmtBody.reserve(body.size()); + + QVector<uint> utf32_string = body.toUcs4(); + + bool insideFontBlock = false; + for (auto &code : utf32_string) { + if (utils::codepointIsEmoji(code)) { + if (!insideFontBlock) { + fmtBody += QStringLiteral("<font face=\"") % UserSettings::instance()->emojiFont() % + QStringLiteral("\">"); + insideFontBlock = true; + } + + } else { + if (insideFontBlock) { fmtBody += QStringLiteral("</font>"); + insideFontBlock = false; + } } + if (QChar::requiresSurrogates(code)) { + QChar emoji[] = {static_cast<ushort>(QChar::highSurrogate(code)), + static_cast<ushort>(QChar::lowSurrogate(code))}; + fmtBody.append(emoji, 2); + } else { + fmtBody.append(QChar(static_cast<ushort>(code))); + } + } + if (insideFontBlock) { + fmtBody += QStringLiteral("</font>"); + } - return fmtBody; + return fmtBody; } void utils::setScaleFactor(float factor) { - if (factor < 1 || factor > 3) - return; + if (factor < 1 || factor > 3) + return; - QSettings settings; - settings.setValue("settings/scale_factor", factor); + QSettings settings; + settings.setValue("settings/scale_factor", factor); } float utils::scaleFactor() { - QSettings settings; - return settings.value("settings/scale_factor", -1).toFloat(); + QSettings settings; + return settings.value("settings/scale_factor", -1).toFloat(); } QString utils::descriptiveTime(const QDateTime &then) { - const auto now = QDateTime::currentDateTime(); - const auto days = then.daysTo(now); + const auto now = QDateTime::currentDateTime(); + const auto days = then.daysTo(now); - if (days == 0) - return QLocale::system().toString(then.time(), QLocale::ShortFormat); - else if (days < 2) - return QString(QCoreApplication::translate("descriptiveTime", "Yesterday")); - else if (days < 7) - return then.toString("dddd"); + if (days == 0) + return QLocale::system().toString(then.time(), QLocale::ShortFormat); + else if (days < 2) + return QString(QCoreApplication::translate("descriptiveTime", "Yesterday")); + else if (days < 7) + return then.toString("dddd"); - return QLocale::system().toString(then.date(), QLocale::ShortFormat); + return QLocale::system().toString(then.date(), QLocale::ShortFormat); } DescInfo @@ -194,630 +193,622 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUser, const QString &displayName) { - using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>; - using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>; - using File = mtx::events::RoomEvent<mtx::events::msg::File>; - using Image = mtx::events::RoomEvent<mtx::events::msg::Image>; - using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>; - using Text = mtx::events::RoomEvent<mtx::events::msg::Text>; - using Video = mtx::events::RoomEvent<mtx::events::msg::Video>; - using CallInvite = mtx::events::RoomEvent<mtx::events::msg::CallInvite>; - using CallAnswer = mtx::events::RoomEvent<mtx::events::msg::CallAnswer>; - using CallHangUp = mtx::events::RoomEvent<mtx::events::msg::CallHangUp>; - using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>; - - if (std::holds_alternative<Audio>(event)) { - return createDescriptionInfo<Audio>(event, localUser, displayName); - } else if (std::holds_alternative<Emote>(event)) { - return createDescriptionInfo<Emote>(event, localUser, displayName); - } else if (std::holds_alternative<File>(event)) { - return createDescriptionInfo<File>(event, localUser, displayName); - } else if (std::holds_alternative<Image>(event)) { - return createDescriptionInfo<Image>(event, localUser, displayName); - } else if (std::holds_alternative<Notice>(event)) { - return createDescriptionInfo<Notice>(event, localUser, displayName); - } else if (std::holds_alternative<Text>(event)) { - return createDescriptionInfo<Text>(event, localUser, displayName); - } else if (std::holds_alternative<Video>(event)) { - return createDescriptionInfo<Video>(event, localUser, displayName); - } else if (std::holds_alternative<CallInvite>(event)) { - return createDescriptionInfo<CallInvite>(event, localUser, displayName); - } else if (std::holds_alternative<CallAnswer>(event)) { - return createDescriptionInfo<CallAnswer>(event, localUser, displayName); - } else if (std::holds_alternative<CallHangUp>(event)) { - return createDescriptionInfo<CallHangUp>(event, localUser, displayName); - } else if (std::holds_alternative<mtx::events::Sticker>(event)) { - return createDescriptionInfo<mtx::events::Sticker>(event, localUser, displayName); - } else if (auto msg = std::get_if<Encrypted>(&event); msg != nullptr) { - const auto sender = QString::fromStdString(msg->sender); - - const auto username = displayName; - const auto ts = QDateTime::fromMSecsSinceEpoch(msg->origin_server_ts); - - DescInfo info; - info.userid = sender; - info.body = QString(" %1").arg( - messageDescription<Encrypted>(username, "", sender == localUser)); - info.timestamp = msg->origin_server_ts; - info.descriptiveTime = utils::descriptiveTime(ts); - info.event_id = QString::fromStdString(msg->event_id); - info.datetime = ts; - - return info; - } + using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>; + using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>; + using File = mtx::events::RoomEvent<mtx::events::msg::File>; + using Image = mtx::events::RoomEvent<mtx::events::msg::Image>; + using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>; + using Text = mtx::events::RoomEvent<mtx::events::msg::Text>; + using Video = mtx::events::RoomEvent<mtx::events::msg::Video>; + using CallInvite = mtx::events::RoomEvent<mtx::events::msg::CallInvite>; + using CallAnswer = mtx::events::RoomEvent<mtx::events::msg::CallAnswer>; + using CallHangUp = mtx::events::RoomEvent<mtx::events::msg::CallHangUp>; + using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>; + + if (std::holds_alternative<Audio>(event)) { + return createDescriptionInfo<Audio>(event, localUser, displayName); + } else if (std::holds_alternative<Emote>(event)) { + return createDescriptionInfo<Emote>(event, localUser, displayName); + } else if (std::holds_alternative<File>(event)) { + return createDescriptionInfo<File>(event, localUser, displayName); + } else if (std::holds_alternative<Image>(event)) { + return createDescriptionInfo<Image>(event, localUser, displayName); + } else if (std::holds_alternative<Notice>(event)) { + return createDescriptionInfo<Notice>(event, localUser, displayName); + } else if (std::holds_alternative<Text>(event)) { + return createDescriptionInfo<Text>(event, localUser, displayName); + } else if (std::holds_alternative<Video>(event)) { + return createDescriptionInfo<Video>(event, localUser, displayName); + } else if (std::holds_alternative<CallInvite>(event)) { + return createDescriptionInfo<CallInvite>(event, localUser, displayName); + } else if (std::holds_alternative<CallAnswer>(event)) { + return createDescriptionInfo<CallAnswer>(event, localUser, displayName); + } else if (std::holds_alternative<CallHangUp>(event)) { + return createDescriptionInfo<CallHangUp>(event, localUser, displayName); + } else if (std::holds_alternative<mtx::events::Sticker>(event)) { + return createDescriptionInfo<mtx::events::Sticker>(event, localUser, displayName); + } else if (auto msg = std::get_if<Encrypted>(&event); msg != nullptr) { + const auto sender = QString::fromStdString(msg->sender); + + const auto username = displayName; + const auto ts = QDateTime::fromMSecsSinceEpoch(msg->origin_server_ts); + + DescInfo info; + info.userid = sender; + info.body = + QString(" %1").arg(messageDescription<Encrypted>(username, "", sender == localUser)); + info.timestamp = msg->origin_server_ts; + info.descriptiveTime = utils::descriptiveTime(ts); + info.event_id = QString::fromStdString(msg->event_id); + info.datetime = ts; + + return info; + } - return DescInfo{}; + return DescInfo{}; } QString utils::firstChar(const QString &input) { - if (input.isEmpty()) - return input; + if (input.isEmpty()) + return input; - for (auto const &c : input.toUcs4()) { - if (QString::fromUcs4(&c, 1) != QString("#")) - return QString::fromUcs4(&c, 1).toUpper(); - } + for (auto const &c : input.toUcs4()) { + if (QString::fromUcs4(&c, 1) != QString("#")) + return QString::fromUcs4(&c, 1).toUpper(); + } - return QString::fromUcs4(&input.toUcs4().at(0), 1).toUpper(); + return QString::fromUcs4(&input.toUcs4().at(0), 1).toUpper(); } QString utils::humanReadableFileSize(uint64_t bytes) { - constexpr static const char *units[] = {"B", "KiB", "MiB", "GiB", "TiB"}; - constexpr static const int length = sizeof(units) / sizeof(units[0]); + constexpr static const char *units[] = {"B", "KiB", "MiB", "GiB", "TiB"}; + constexpr static const int length = sizeof(units) / sizeof(units[0]); - int u = 0; - double size = static_cast<double>(bytes); - while (size >= 1024.0 && u < length) { - ++u; - size /= 1024.0; - } + int u = 0; + double size = static_cast<double>(bytes); + while (size >= 1024.0 && u < length) { + ++u; + size /= 1024.0; + } - return QString::number(size, 'g', 4) + ' ' + units[u]; + return QString::number(size, 'g', 4) + ' ' + units[u]; } int utils::levenshtein_distance(const std::string &s1, const std::string &s2) { - const auto nlen = s1.size(); - const auto hlen = s2.size(); + const auto nlen = s1.size(); + const auto hlen = s2.size(); - if (hlen == 0) - return -1; - if (nlen == 1) - return (int)s2.find(s1); + if (hlen == 0) + return -1; + if (nlen == 1) + return (int)s2.find(s1); - std::vector<int> row1(hlen + 1, 0); + std::vector<int> row1(hlen + 1, 0); - for (size_t i = 0; i < nlen; ++i) { - std::vector<int> row2(1, (int)i + 1); - - for (size_t j = 0; j < hlen; ++j) { - const int cost = s1[i] != s2[j]; - row2.push_back( - std::min(row1[j + 1] + 1, std::min(row2[j] + 1, row1[j] + cost))); - } + for (size_t i = 0; i < nlen; ++i) { + std::vector<int> row2(1, (int)i + 1); - row1.swap(row2); + for (size_t j = 0; j < hlen; ++j) { + const int cost = s1[i] != s2[j]; + row2.push_back(std::min(row1[j + 1] + 1, std::min(row2[j] + 1, row1[j] + cost))); } - return *std::min_element(row1.begin(), row1.end()); + row1.swap(row2); + } + + return *std::min_element(row1.begin(), row1.end()); } QString utils::event_body(const mtx::events::collections::TimelineEvents &e) { - using namespace mtx::events; - if (auto ev = std::get_if<RoomEvent<msg::Audio>>(&e); ev != nullptr) - return QString::fromStdString(ev->content.body); - if (auto ev = std::get_if<RoomEvent<msg::Emote>>(&e); ev != nullptr) - return QString::fromStdString(ev->content.body); - if (auto ev = std::get_if<RoomEvent<msg::File>>(&e); ev != nullptr) - return QString::fromStdString(ev->content.body); - if (auto ev = std::get_if<RoomEvent<msg::Image>>(&e); ev != nullptr) - return QString::fromStdString(ev->content.body); - if (auto ev = std::get_if<RoomEvent<msg::Notice>>(&e); ev != nullptr) - return QString::fromStdString(ev->content.body); - if (auto ev = std::get_if<RoomEvent<msg::Text>>(&e); ev != nullptr) - return QString::fromStdString(ev->content.body); - if (auto ev = std::get_if<RoomEvent<msg::Video>>(&e); ev != nullptr) - return QString::fromStdString(ev->content.body); - - return ""; + using namespace mtx::events; + if (auto ev = std::get_if<RoomEvent<msg::Audio>>(&e); ev != nullptr) + return QString::fromStdString(ev->content.body); + if (auto ev = std::get_if<RoomEvent<msg::Emote>>(&e); ev != nullptr) + return QString::fromStdString(ev->content.body); + if (auto ev = std::get_if<RoomEvent<msg::File>>(&e); ev != nullptr) + return QString::fromStdString(ev->content.body); + if (auto ev = std::get_if<RoomEvent<msg::Image>>(&e); ev != nullptr) + return QString::fromStdString(ev->content.body); + if (auto ev = std::get_if<RoomEvent<msg::Notice>>(&e); ev != nullptr) + return QString::fromStdString(ev->content.body); + if (auto ev = std::get_if<RoomEvent<msg::Text>>(&e); ev != nullptr) + return QString::fromStdString(ev->content.body); + if (auto ev = std::get_if<RoomEvent<msg::Video>>(&e); ev != nullptr) + return QString::fromStdString(ev->content.body); + + return ""; } QPixmap utils::scaleImageToPixmap(const QImage &img, int size) { - if (img.isNull()) - return QPixmap(); + if (img.isNull()) + return QPixmap(); - // Deprecated in 5.13: const double sz = - // std::ceil(QApplication::desktop()->screen()->devicePixelRatioF() * (double)size); - const double sz = - std::ceil(QGuiApplication::primaryScreen()->devicePixelRatio() * (double)size); - return QPixmap::fromImage( - img.scaled(sz, sz, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + // Deprecated in 5.13: const double sz = + // std::ceil(QApplication::desktop()->screen()->devicePixelRatioF() * (double)size); + const double sz = + std::ceil(QGuiApplication::primaryScreen()->devicePixelRatio() * (double)size); + return QPixmap::fromImage(img.scaled(sz, sz, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } QPixmap utils::scaleDown(uint64_t maxWidth, uint64_t maxHeight, const QPixmap &source) { - if (source.isNull()) - return QPixmap(); + if (source.isNull()) + return QPixmap(); - const double widthRatio = (double)maxWidth / (double)source.width(); - const double heightRatio = (double)maxHeight / (double)source.height(); - const double minAspectRatio = std::min(widthRatio, heightRatio); + const double widthRatio = (double)maxWidth / (double)source.width(); + const double heightRatio = (double)maxHeight / (double)source.height(); + const double minAspectRatio = std::min(widthRatio, heightRatio); - // Size of the output image. - int w, h = 0; + // Size of the output image. + int w, h = 0; - if (minAspectRatio > 1) { - w = source.width(); - h = source.height(); - } else { - w = source.width() * minAspectRatio; - h = source.height() * minAspectRatio; - } + if (minAspectRatio > 1) { + w = source.width(); + h = source.height(); + } else { + w = source.width() * minAspectRatio; + h = source.height() * minAspectRatio; + } - return source.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + return source.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } QString utils::mxcToHttp(const QUrl &url, const QString &server, int port) { - auto mxcParts = mtx::client::utils::parse_mxc_url(url.toString().toStdString()); + auto mxcParts = mtx::client::utils::parse_mxc_url(url.toString().toStdString()); - return QString("https://%1:%2/_matrix/media/r0/download/%3/%4") - .arg(server) - .arg(port) - .arg(QString::fromStdString(mxcParts.server)) - .arg(QString::fromStdString(mxcParts.media_id)); + return QString("https://%1:%2/_matrix/media/r0/download/%3/%4") + .arg(server) + .arg(port) + .arg(QString::fromStdString(mxcParts.server)) + .arg(QString::fromStdString(mxcParts.media_id)); } QString utils::humanReadableFingerprint(const std::string &ed25519) { - return humanReadableFingerprint(QString::fromStdString(ed25519)); + return humanReadableFingerprint(QString::fromStdString(ed25519)); } QString utils::humanReadableFingerprint(const QString &ed25519) { - QString fingerprint; - for (int i = 0; i < ed25519.length(); i = i + 4) { - fingerprint.append(ed25519.midRef(i, 4)); - if (i > 0 && i % 16 == 12) - fingerprint.append('\n'); - else if (i < ed25519.length()) - fingerprint.append(' '); - } - return fingerprint; + QString fingerprint; + for (int i = 0; i < ed25519.length(); i = i + 4) { + fingerprint.append(ed25519.midRef(i, 4)); + if (i > 0 && i % 16 == 12) + fingerprint.append('\n'); + else if (i < ed25519.length()) + fingerprint.append(' '); + } + return fingerprint; } QString utils::linkifyMessage(const QString &body) { - // Convert to valid XML. - auto doc = body; - doc.replace(conf::strings::url_regex, conf::strings::url_html); - doc.replace(QRegularExpression("\\b(?<![\"'])(?>(matrix:[\\S]{5,}))(?![\"'])\\b"), - conf::strings::url_html); + // Convert to valid XML. + auto doc = body; + doc.replace(conf::strings::url_regex, conf::strings::url_html); + doc.replace(QRegularExpression("\\b(?<![\"'])(?>(matrix:[\\S]{5,}))(?![\"'])\\b"), + conf::strings::url_html); - return doc; + return doc; } QString utils::escapeBlacklistedHtml(const QString &rawStr) { - static const std::array allowedTags = { - "font", "/font", "del", "/del", "h1", "/h1", "h2", "/h2", - "h3", "/h3", "h4", "/h4", "h5", "/h5", "h6", "/h6", - "blockquote", "/blockquote", "p", "/p", "a", "/a", "ul", "/ul", - "ol", "/ol", "sup", "/sup", "sub", "/sub", "li", "/li", - "b", "/b", "i", "/i", "u", "/u", "strong", "/strong", - "em", "/em", "strike", "/strike", "code", "/code", "hr", "/hr", - "br", "br/", "div", "/div", "table", "/table", "thead", "/thead", - "tbody", "/tbody", "tr", "/tr", "th", "/th", "td", "/td", - "caption", "/caption", "pre", "/pre", "span", "/span", "img", "/img"}; - QByteArray data = rawStr.toUtf8(); - QByteArray buffer; - const int length = data.size(); - buffer.reserve(length); - bool escapingTag = false; - for (int pos = 0; pos != length; ++pos) { - switch (data.at(pos)) { - case '<': { - bool oneTagMatched = false; - const int endPos = - static_cast<int>(std::min(static_cast<size_t>(data.indexOf('>', pos)), - static_cast<size_t>(data.indexOf(' ', pos)))); - - auto mid = data.mid(pos + 1, endPos - pos - 1); - for (const auto &tag : allowedTags) { - // TODO: Check src and href attribute - if (mid.toLower() == tag) { - oneTagMatched = true; - } - } - if (oneTagMatched) - buffer.append('<'); - else { - escapingTag = true; - buffer.append("<"); - } - break; - } - case '>': - if (escapingTag) { - buffer.append(">"); - escapingTag = false; - } else - buffer.append('>'); - break; - default: - buffer.append(data.at(pos)); - break; + static const std::array allowedTags = { + "font", "/font", "del", "/del", "h1", "/h1", "h2", "/h2", + "h3", "/h3", "h4", "/h4", "h5", "/h5", "h6", "/h6", + "blockquote", "/blockquote", "p", "/p", "a", "/a", "ul", "/ul", + "ol", "/ol", "sup", "/sup", "sub", "/sub", "li", "/li", + "b", "/b", "i", "/i", "u", "/u", "strong", "/strong", + "em", "/em", "strike", "/strike", "code", "/code", "hr", "/hr", + "br", "br/", "div", "/div", "table", "/table", "thead", "/thead", + "tbody", "/tbody", "tr", "/tr", "th", "/th", "td", "/td", + "caption", "/caption", "pre", "/pre", "span", "/span", "img", "/img"}; + QByteArray data = rawStr.toUtf8(); + QByteArray buffer; + const int length = data.size(); + buffer.reserve(length); + bool escapingTag = false; + for (int pos = 0; pos != length; ++pos) { + switch (data.at(pos)) { + case '<': { + bool oneTagMatched = false; + const int endPos = + static_cast<int>(std::min(static_cast<size_t>(data.indexOf('>', pos)), + static_cast<size_t>(data.indexOf(' ', pos)))); + + auto mid = data.mid(pos + 1, endPos - pos - 1); + for (const auto &tag : allowedTags) { + // TODO: Check src and href attribute + if (mid.toLower() == tag) { + oneTagMatched = true; } + } + if (oneTagMatched) + buffer.append('<'); + else { + escapingTag = true; + buffer.append("<"); + } + break; } - return QString::fromUtf8(buffer); + case '>': + if (escapingTag) { + buffer.append(">"); + escapingTag = false; + } else + buffer.append('>'); + break; + default: + buffer.append(data.at(pos)); + break; + } + } + return QString::fromUtf8(buffer); } QString utils::markdownToHtml(const QString &text, bool rainbowify) { - const auto str = text.toUtf8(); - cmark_node *const node = - cmark_parse_document(str.constData(), str.size(), CMARK_OPT_UNSAFE); - - if (rainbowify) { - // create iterator over node - cmark_iter *iter = cmark_iter_new(node); - - cmark_event_type ev_type; - - // First loop to get total text length - int textLen = 0; - while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { - cmark_node *cur = cmark_iter_get_node(iter); - // only text nodes (no code or semilar) - if (cmark_node_get_type(cur) != CMARK_NODE_TEXT) - continue; - // count up by length of current node's text - QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme, - QString(cmark_node_get_literal(cur))); - while (tbf.toNextBoundary() != -1) - textLen++; - } + const auto str = text.toUtf8(); + cmark_node *const node = cmark_parse_document(str.constData(), str.size(), CMARK_OPT_UNSAFE); + + if (rainbowify) { + // create iterator over node + cmark_iter *iter = cmark_iter_new(node); + + cmark_event_type ev_type; + + // First loop to get total text length + int textLen = 0; + while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { + cmark_node *cur = cmark_iter_get_node(iter); + // only text nodes (no code or semilar) + if (cmark_node_get_type(cur) != CMARK_NODE_TEXT) + continue; + // count up by length of current node's text + QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme, + QString(cmark_node_get_literal(cur))); + while (tbf.toNextBoundary() != -1) + textLen++; + } - // create new iter to start over - cmark_iter_free(iter); - iter = cmark_iter_new(node); - - // Second loop to rainbowify - int charIdx = 0; - while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { - cmark_node *cur = cmark_iter_get_node(iter); - // only text nodes (no code or semilar) - if (cmark_node_get_type(cur) != CMARK_NODE_TEXT) - continue; - - // get text in current node - QString nodeText(cmark_node_get_literal(cur)); - // create buffer to append rainbow text to - QString buf; - int boundaryStart = 0; - int boundaryEnd = 0; - // use QTextBoundaryFinder to iterate ofer graphemes - QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme, - nodeText); - while ((boundaryEnd = tbf.toNextBoundary()) != -1) { - charIdx++; - // Split text to get current char - auto curChar = - nodeText.midRef(boundaryStart, boundaryEnd - boundaryStart); - boundaryStart = boundaryEnd; - // Don't rainbowify whitespaces - if (curChar.trimmed().isEmpty() || - codepointIsEmoji(curChar.toUcs4().first())) { - buf.append(curChar); - continue; - } - - // get correct color for char index - // Use colors as described here: - // https://shark.comfsm.fm/~dleeling/cis/hsl_rainbow.html - auto color = - QColor::fromHslF((charIdx - 1.0) / textLen * (5. / 6.), 0.9, 0.5); - // format color for HTML - auto colorString = color.name(QColor::NameFormat::HexRgb); - // create HTML element for current char - auto curCharColored = QString("<font color=\"%0\">%1</font>") - .arg(colorString) - .arg(curChar); - // append colored HTML element to buffer - buf.append(curCharColored); - } - - // create HTML_INLINE node to prevent HTML from being escaped - auto htmlNode = cmark_node_new(CMARK_NODE_HTML_INLINE); - // set content of HTML node to buffer contents - cmark_node_set_literal(htmlNode, buf.toUtf8().data()); - // replace current node with HTML node - cmark_node_replace(cur, htmlNode); - // free memory of old node - cmark_node_free(cur); + // create new iter to start over + cmark_iter_free(iter); + iter = cmark_iter_new(node); + + // Second loop to rainbowify + int charIdx = 0; + while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { + cmark_node *cur = cmark_iter_get_node(iter); + // only text nodes (no code or semilar) + if (cmark_node_get_type(cur) != CMARK_NODE_TEXT) + continue; + + // get text in current node + QString nodeText(cmark_node_get_literal(cur)); + // create buffer to append rainbow text to + QString buf; + int boundaryStart = 0; + int boundaryEnd = 0; + // use QTextBoundaryFinder to iterate ofer graphemes + QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme, nodeText); + while ((boundaryEnd = tbf.toNextBoundary()) != -1) { + charIdx++; + // Split text to get current char + auto curChar = nodeText.midRef(boundaryStart, boundaryEnd - boundaryStart); + boundaryStart = boundaryEnd; + // Don't rainbowify whitespaces + if (curChar.trimmed().isEmpty() || codepointIsEmoji(curChar.toUcs4().first())) { + buf.append(curChar); + continue; } - cmark_iter_free(iter); + // get correct color for char index + // Use colors as described here: + // https://shark.comfsm.fm/~dleeling/cis/hsl_rainbow.html + auto color = QColor::fromHslF((charIdx - 1.0) / textLen * (5. / 6.), 0.9, 0.5); + // format color for HTML + auto colorString = color.name(QColor::NameFormat::HexRgb); + // create HTML element for current char + auto curCharColored = + QString("<font color=\"%0\">%1</font>").arg(colorString).arg(curChar); + // append colored HTML element to buffer + buf.append(curCharColored); + } + + // create HTML_INLINE node to prevent HTML from being escaped + auto htmlNode = cmark_node_new(CMARK_NODE_HTML_INLINE); + // set content of HTML node to buffer contents + cmark_node_set_literal(htmlNode, buf.toUtf8().data()); + // replace current node with HTML node + cmark_node_replace(cur, htmlNode); + // free memory of old node + cmark_node_free(cur); } - const char *tmp_buf = cmark_render_html(node, CMARK_OPT_UNSAFE); - // Copy the null terminated output buffer. - std::string html(tmp_buf); + cmark_iter_free(iter); + } - // The buffer is no longer needed. - free((char *)tmp_buf); + const char *tmp_buf = cmark_render_html(node, CMARK_OPT_UNSAFE); + // Copy the null terminated output buffer. + std::string html(tmp_buf); - auto result = linkifyMessage(escapeBlacklistedHtml(QString::fromStdString(html))).trimmed(); + // The buffer is no longer needed. + free((char *)tmp_buf); - if (result.count("<p>") == 1 && result.startsWith("<p>") && result.endsWith("</p>")) { - result = result.mid(3, result.size() - 3 - 4); - } + auto result = linkifyMessage(escapeBlacklistedHtml(QString::fromStdString(html))).trimmed(); - return result; -} + if (result.count("<p>") == 1 && result.startsWith("<p>") && result.endsWith("</p>")) { + result = result.mid(3, result.size() - 3 - 4); + } -QString -utils::getFormattedQuoteBody(const RelatedInfo &related, const QString &html) -{ - auto getFormattedBody = [related]() -> QString { - using MsgType = mtx::events::MessageType; - - switch (related.type) { - case MsgType::File: { - return "sent a file."; - } - case MsgType::Image: { - return "sent an image."; - } - case MsgType::Audio: { - return "sent an audio file."; - } - case MsgType::Video: { - return "sent a video"; - } - default: { - return related.quoted_formatted_body; - } - } - }; - return QString("<mx-reply><blockquote><a " - "href=\"https://matrix.to/#/%1/%2\">In reply " - "to</a> <a href=\"https://matrix.to/#/%3\">%4</a><br" - "/>%5</blockquote></mx-reply>") - .arg(related.room, - QString::fromStdString(related.related_event), - related.quoted_user, - related.quoted_user, - getFormattedBody()) + - html; + return result; } QString -utils::getQuoteBody(const RelatedInfo &related) +utils::getFormattedQuoteBody(const RelatedInfo &related, const QString &html) { + auto getFormattedBody = [related]() -> QString { using MsgType = mtx::events::MessageType; switch (related.type) { case MsgType::File: { - return "sent a file."; + return "sent a file."; } case MsgType::Image: { - return "sent an image."; + return "sent an image."; } case MsgType::Audio: { - return "sent an audio file."; + return "sent an audio file."; } case MsgType::Video: { - return "sent a video"; + return "sent a video"; } default: { - return related.quoted_body; + return related.quoted_formatted_body; } } + }; + return QString("<mx-reply><blockquote><a " + "href=\"https://matrix.to/#/%1/%2\">In reply " + "to</a> <a href=\"https://matrix.to/#/%3\">%4</a><br" + "/>%5</blockquote></mx-reply>") + .arg(related.room, + QString::fromStdString(related.related_event), + related.quoted_user, + related.quoted_user, + getFormattedBody()) + + html; +} + +QString +utils::getQuoteBody(const RelatedInfo &related) +{ + using MsgType = mtx::events::MessageType; + + switch (related.type) { + case MsgType::File: { + return "sent a file."; + } + case MsgType::Image: { + return "sent an image."; + } + case MsgType::Audio: { + return "sent an audio file."; + } + case MsgType::Video: { + return "sent a video"; + } + default: { + return related.quoted_body; + } + } } QString utils::linkColor() { - const auto theme = UserSettings::instance()->theme(); + const auto theme = UserSettings::instance()->theme(); - if (theme == "light") { - return "#0077b5"; - } else if (theme == "dark") { - return "#38A3D8"; - } else { - return QPalette().color(QPalette::Link).name(); - } + if (theme == "light") { + return "#0077b5"; + } else if (theme == "dark") { + return "#38A3D8"; + } else { + return QPalette().color(QPalette::Link).name(); + } } uint32_t utils::hashQString(const QString &input) { - uint32_t hash = 0; + uint32_t hash = 0; - for (int i = 0; i < input.length(); i++) { - hash = input.at(i).digitValue() + ((hash << 5) - hash); - } + for (int i = 0; i < input.length(); i++) { + hash = input.at(i).digitValue() + ((hash << 5) - hash); + } - return hash; + return hash; } QColor utils::generateContrastingHexColor(const QString &input, const QColor &backgroundCol) { - const qreal backgroundLum = luminance(backgroundCol); - - // Create a color for the input - auto hash = hashQString(input); - // create a hue value based on the hash of the input. - auto userHue = static_cast<int>(hash % 360); - // start with moderate saturation and lightness values. - auto sat = 220; - auto lightness = 125; - - // converting to a QColor makes the luminance calc easier. - QColor inputColor = QColor::fromHsl(userHue, sat, lightness); - - // calculate the initial luminance and contrast of the - // generated color. It's possible that no additional - // work will be necessary. - auto lum = luminance(inputColor); - auto contrast = computeContrast(lum, backgroundLum); - - // If the contrast doesn't meet our criteria, - // try again and again until they do by modifying first - // the lightness and then the saturation of the color. - int iterationCount = 9; - while (contrast < 5) { - // if our lightness is at it's bounds, try changing - // saturation instead. - if (lightness == 242 || lightness == 13) { - qreal newSat = qBound(26.0, sat * 1.25, 242.0); - - inputColor.setHsl(userHue, qFloor(newSat), lightness); - auto tmpLum = luminance(inputColor); - auto higherContrast = computeContrast(tmpLum, backgroundLum); - if (higherContrast > contrast) { - contrast = higherContrast; - sat = newSat; - } else { - newSat = qBound(26.0, sat / 1.25, 242.0); - inputColor.setHsl(userHue, qFloor(newSat), lightness); - tmpLum = luminance(inputColor); - auto lowerContrast = computeContrast(tmpLum, backgroundLum); - if (lowerContrast > contrast) { - contrast = lowerContrast; - sat = newSat; - } - } - } else { - qreal newLightness = qBound(13.0, lightness * 1.25, 242.0); - - inputColor.setHsl(userHue, sat, qFloor(newLightness)); - - auto tmpLum = luminance(inputColor); - auto higherContrast = computeContrast(tmpLum, backgroundLum); - - // Check to make sure we have actually improved contrast - if (higherContrast > contrast) { - contrast = higherContrast; - lightness = newLightness; - // otherwise, try going the other way instead. - } else { - newLightness = qBound(13.0, lightness / 1.25, 242.0); - inputColor.setHsl(userHue, sat, qFloor(newLightness)); - tmpLum = luminance(inputColor); - auto lowerContrast = computeContrast(tmpLum, backgroundLum); - if (lowerContrast > contrast) { - contrast = lowerContrast; - lightness = newLightness; - } - } + const qreal backgroundLum = luminance(backgroundCol); + + // Create a color for the input + auto hash = hashQString(input); + // create a hue value based on the hash of the input. + auto userHue = static_cast<int>(hash % 360); + // start with moderate saturation and lightness values. + auto sat = 220; + auto lightness = 125; + + // converting to a QColor makes the luminance calc easier. + QColor inputColor = QColor::fromHsl(userHue, sat, lightness); + + // calculate the initial luminance and contrast of the + // generated color. It's possible that no additional + // work will be necessary. + auto lum = luminance(inputColor); + auto contrast = computeContrast(lum, backgroundLum); + + // If the contrast doesn't meet our criteria, + // try again and again until they do by modifying first + // the lightness and then the saturation of the color. + int iterationCount = 9; + while (contrast < 5) { + // if our lightness is at it's bounds, try changing + // saturation instead. + if (lightness == 242 || lightness == 13) { + qreal newSat = qBound(26.0, sat * 1.25, 242.0); + + inputColor.setHsl(userHue, qFloor(newSat), lightness); + auto tmpLum = luminance(inputColor); + auto higherContrast = computeContrast(tmpLum, backgroundLum); + if (higherContrast > contrast) { + contrast = higherContrast; + sat = newSat; + } else { + newSat = qBound(26.0, sat / 1.25, 242.0); + inputColor.setHsl(userHue, qFloor(newSat), lightness); + tmpLum = luminance(inputColor); + auto lowerContrast = computeContrast(tmpLum, backgroundLum); + if (lowerContrast > contrast) { + contrast = lowerContrast; + sat = newSat; } - - // don't loop forever, just give up at some point! - // Someone smart may find a better solution - if (--iterationCount < 0) - break; + } + } else { + qreal newLightness = qBound(13.0, lightness * 1.25, 242.0); + + inputColor.setHsl(userHue, sat, qFloor(newLightness)); + + auto tmpLum = luminance(inputColor); + auto higherContrast = computeContrast(tmpLum, backgroundLum); + + // Check to make sure we have actually improved contrast + if (higherContrast > contrast) { + contrast = higherContrast; + lightness = newLightness; + // otherwise, try going the other way instead. + } else { + newLightness = qBound(13.0, lightness / 1.25, 242.0); + inputColor.setHsl(userHue, sat, qFloor(newLightness)); + tmpLum = luminance(inputColor); + auto lowerContrast = computeContrast(tmpLum, backgroundLum); + if (lowerContrast > contrast) { + contrast = lowerContrast; + lightness = newLightness; + } + } } - // get the hex value of the generated color. - auto colorHex = inputColor.name(); + // don't loop forever, just give up at some point! + // Someone smart may find a better solution + if (--iterationCount < 0) + break; + } - return colorHex; + // get the hex value of the generated color. + auto colorHex = inputColor.name(); + + return colorHex; } qreal utils::computeContrast(const qreal &one, const qreal &two) { - auto ratio = (one + 0.05) / (two + 0.05); + auto ratio = (one + 0.05) / (two + 0.05); - if (two > one) { - ratio = 1 / ratio; - } + if (two > one) { + ratio = 1 / ratio; + } - return ratio; + return ratio; } qreal utils::luminance(const QColor &col) { - int colRgb[3] = {col.red(), col.green(), col.blue()}; - qreal lumRgb[3]; + int colRgb[3] = {col.red(), col.green(), col.blue()}; + qreal lumRgb[3]; - for (int i = 0; i < 3; i++) { - qreal v = colRgb[i] / 255.0; - lumRgb[i] = v <= 0.03928 ? v / 12.92 : qPow((v + 0.055) / 1.055, 2.4); - } + for (int i = 0; i < 3; i++) { + qreal v = colRgb[i] / 255.0; + lumRgb[i] = v <= 0.03928 ? v / 12.92 : qPow((v + 0.055) / 1.055, 2.4); + } - auto lum = lumRgb[0] * 0.2126 + lumRgb[1] * 0.7152 + lumRgb[2] * 0.0722; + auto lum = lumRgb[0] * 0.2126 + lumRgb[1] * 0.7152 + lumRgb[2] * 0.0722; - return lum; + return lum; } void utils::centerWidget(QWidget *widget, QWidget *parent) { - auto findCenter = [childRect = widget->rect()](QRect hostRect) -> QPoint { - return QPoint(hostRect.center().x() - (childRect.width() * 0.5), - hostRect.center().y() - (childRect.height() * 0.5)); - }; + auto findCenter = [childRect = widget->rect()](QRect hostRect) -> QPoint { + return QPoint(hostRect.center().x() - (childRect.width() * 0.5), + hostRect.center().y() - (childRect.height() * 0.5)); + }; - if (parent) { - widget->move(parent->window()->frameGeometry().topLeft() + - parent->window()->rect().center() - widget->rect().center()); - return; - } + if (parent) { + widget->move(parent->window()->frameGeometry().topLeft() + + parent->window()->rect().center() - widget->rect().center()); + return; + } - // Deprecated in 5.13: widget->move(findCenter(QApplication::desktop()->screenGeometry())); - widget->move(findCenter(QGuiApplication::primaryScreen()->geometry())); + // Deprecated in 5.13: widget->move(findCenter(QApplication::desktop()->screenGeometry())); + widget->move(findCenter(QGuiApplication::primaryScreen()->geometry())); } void utils::restoreCombobox(QComboBox *combo, const QString &value) { - for (auto i = 0; i < combo->count(); ++i) { - if (value == combo->itemText(i)) { - combo->setCurrentIndex(i); - break; - } + for (auto i = 0; i < combo->count(); ++i) { + if (value == combo->itemText(i)) { + combo->setCurrentIndex(i); + break; } + } } QImage utils::readImageFromFile(const QString &filename) { - QImageReader reader(filename); - reader.setAutoTransform(true); - return reader.read(); + QImageReader reader(filename); + reader.setAutoTransform(true); + return reader.read(); } QImage utils::readImage(const QByteArray &data) { - QBuffer buf; - buf.setData(data); - QImageReader reader(&buf); - reader.setAutoTransform(true); - return reader.read(); + QBuffer buf; + buf.setData(data); + QImageReader reader(&buf); + reader.setAutoTransform(true); + return reader.read(); } bool utils::isReply(const mtx::events::collections::TimelineEvents &e) { - return mtx::accessors::relations(e).reply_to().has_value(); + return mtx::accessors::relations(e).reply_to().has_value(); } |