Add ability to highlight messages based on user badges (#1704)
Co-authored-by: Paweł <zneix@zneix.eu> Co-authored-by: 23rd <23rd@vivaldi.net> Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
#include "BadgeHighlightModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// commandmodel
|
||||
BadgeHighlightModel::BadgeHighlightModel(QObject *parent)
|
||||
: SignalVectorModel<HighlightBadge>(5, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn vector item into model row
|
||||
HighlightBadge BadgeHighlightModel::getItemFromRow(
|
||||
std::vector<QStandardItem *> &row, const HighlightBadge &original)
|
||||
{
|
||||
using Column = BadgeHighlightModel::Column;
|
||||
|
||||
// In order for old messages to update their highlight color, we need to
|
||||
// update the highlight color here.
|
||||
auto highlightColor = original.getColor();
|
||||
*highlightColor =
|
||||
row[Column::Color]->data(Qt::DecorationRole).value<QColor>();
|
||||
|
||||
return HighlightBadge{
|
||||
original.badgeName(),
|
||||
row[Column::Badge]->data(Qt::DisplayRole).toString(),
|
||||
row[Column::FlashTaskbar]->data(Qt::CheckStateRole).toBool(),
|
||||
row[Column::PlaySound]->data(Qt::CheckStateRole).toBool(),
|
||||
row[Column::SoundPath]->data(Qt::UserRole).toString(),
|
||||
highlightColor};
|
||||
}
|
||||
|
||||
// row into vector item
|
||||
void BadgeHighlightModel::getRowFromItem(const HighlightBadge &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
using QIconPtr = std::shared_ptr<QIcon>;
|
||||
using Column = BadgeHighlightModel::Column;
|
||||
|
||||
setStringItem(row[Column::Badge], item.displayName(), false, true);
|
||||
setBoolItem(row[Column::FlashTaskbar], item.hasAlert());
|
||||
setBoolItem(row[Column::PlaySound], item.hasSound());
|
||||
setFilePathItem(row[Column::SoundPath], item.getSoundUrl());
|
||||
setColorItem(row[Column::Color], *item.getColor());
|
||||
|
||||
TwitchBadges::instance()->getBadgeIcon(
|
||||
item.badgeName(), [item, row](QString /*name*/, const QIconPtr pixmap) {
|
||||
row[Column::Badge]->setData(QVariant(*pixmap), Qt::DecorationRole);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/highlights/HighlightBadge.hpp"
|
||||
#include "providers/twitch/TwitchBadges.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class HighlightController;
|
||||
|
||||
class BadgeHighlightModel : public SignalVectorModel<HighlightBadge>
|
||||
{
|
||||
public:
|
||||
explicit BadgeHighlightModel(QObject *parent);
|
||||
|
||||
enum Column {
|
||||
Badge = 0,
|
||||
FlashTaskbar = 1,
|
||||
PlaySound = 2,
|
||||
SoundPath = 3,
|
||||
Color = 4
|
||||
};
|
||||
|
||||
protected:
|
||||
// vector into model row
|
||||
virtual HighlightBadge getItemFromRow(
|
||||
std::vector<QStandardItem *> &row,
|
||||
const HighlightBadge &original) override;
|
||||
|
||||
virtual void getRowFromItem(const HighlightBadge &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,121 @@
|
||||
#include "HighlightBadge.hpp"
|
||||
|
||||
#include "singletons/Resources.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
QColor HighlightBadge::FALLBACK_HIGHLIGHT_COLOR = QColor(127, 63, 73, 127);
|
||||
|
||||
bool HighlightBadge::operator==(const HighlightBadge &other) const
|
||||
{
|
||||
return std::tie(this->badgeName_, this->displayName_, this->hasSound_,
|
||||
this->hasAlert_, this->soundUrl_, this->color_) ==
|
||||
std::tie(other.badgeName_, other.displayName_, other.hasSound_,
|
||||
other.hasAlert_, other.soundUrl_, other.color_);
|
||||
}
|
||||
|
||||
HighlightBadge::HighlightBadge(const QString &badgeName,
|
||||
const QString &displayName, bool hasAlert,
|
||||
bool hasSound, const QString &soundUrl,
|
||||
QColor color)
|
||||
: HighlightBadge(badgeName, displayName, hasAlert, hasSound, soundUrl,
|
||||
std::make_shared<QColor>(color))
|
||||
{
|
||||
}
|
||||
|
||||
HighlightBadge::HighlightBadge(const QString &badgeName,
|
||||
const QString &displayName, bool hasAlert,
|
||||
bool hasSound, const QString &soundUrl,
|
||||
std::shared_ptr<QColor> color)
|
||||
: badgeName_(badgeName)
|
||||
, displayName_(displayName)
|
||||
, hasAlert_(hasAlert)
|
||||
, hasSound_(hasSound)
|
||||
, soundUrl_(soundUrl)
|
||||
, color_(color)
|
||||
{
|
||||
// check badgeName at initialization to reduce cost per isMatch call
|
||||
this->hasVersions_ = badgeName.contains("/");
|
||||
this->isMulti_ = badgeName.contains(",");
|
||||
if (this->isMulti_)
|
||||
{
|
||||
this->badges_ = badgeName.split(",");
|
||||
}
|
||||
}
|
||||
|
||||
const QString &HighlightBadge::badgeName() const
|
||||
{
|
||||
return this->badgeName_;
|
||||
}
|
||||
|
||||
const QString &HighlightBadge::displayName() const
|
||||
{
|
||||
return this->displayName_;
|
||||
}
|
||||
|
||||
bool HighlightBadge::hasAlert() const
|
||||
{
|
||||
return this->hasAlert_;
|
||||
}
|
||||
|
||||
bool HighlightBadge::hasSound() const
|
||||
{
|
||||
return this->hasSound_;
|
||||
}
|
||||
|
||||
bool HighlightBadge::isMatch(const Badge &badge) const
|
||||
{
|
||||
if (this->isMulti_)
|
||||
{
|
||||
for (const auto &id : this->badges_)
|
||||
{
|
||||
if (this->compare(id, badge))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->compare(this->badgeName_, badge);
|
||||
}
|
||||
}
|
||||
|
||||
bool HighlightBadge::compare(const QString &id, const Badge &badge) const
|
||||
{
|
||||
if (this->hasVersions_)
|
||||
{
|
||||
auto parts = id.split("/");
|
||||
if (parts.size() == 2)
|
||||
{
|
||||
return parts.at(0).compare(badge.key_, Qt::CaseInsensitive) == 0 &&
|
||||
parts.at(1).compare(badge.value_, Qt::CaseInsensitive) == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return parts.at(0).compare(badge.key_, Qt::CaseInsensitive) == 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return id.compare(badge.key_, Qt::CaseInsensitive) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool HighlightBadge::hasCustomSound() const
|
||||
{
|
||||
return !this->soundUrl_.isEmpty();
|
||||
}
|
||||
|
||||
const QUrl &HighlightBadge::getSoundUrl() const
|
||||
{
|
||||
return this->soundUrl_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<QColor> HighlightBadge::getColor() const
|
||||
{
|
||||
return this->color_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include "providers/twitch/TwitchBadge.hpp"
|
||||
#include "util/RapidJsonSerializeQString.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <pajlada/serialize.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
class HighlightBadge
|
||||
{
|
||||
public:
|
||||
bool operator==(const HighlightBadge &other) const;
|
||||
|
||||
HighlightBadge(const QString &badgeName, const QString &displayName,
|
||||
bool hasAlert, bool hasSound, const QString &soundUrl,
|
||||
QColor color);
|
||||
|
||||
HighlightBadge(const QString &badgeName, const QString &displayName,
|
||||
bool hasAlert, bool hasSound, const QString &soundUrl,
|
||||
std::shared_ptr<QColor> color);
|
||||
|
||||
const QString &badgeName() const;
|
||||
const QString &displayName() const;
|
||||
bool hasAlert() const;
|
||||
bool hasSound() const;
|
||||
bool isMatch(const Badge &badge) const;
|
||||
|
||||
/**
|
||||
* @brief Check if this highlight phrase has a custom sound set.
|
||||
*
|
||||
* Note that this method only checks whether the path to the custom sound
|
||||
* is not empty. It does not check whether the file still exists, is a
|
||||
* sound file, or anything else.
|
||||
*
|
||||
* @return true, if the custom sound file path is not empty, false otherwise
|
||||
*/
|
||||
bool hasCustomSound() const;
|
||||
|
||||
const QUrl &getSoundUrl() const;
|
||||
const std::shared_ptr<QColor> getColor() const;
|
||||
|
||||
/*
|
||||
* XXX: Use the constexpr constructor here once we are building with
|
||||
* Qt>=5.13.
|
||||
*/
|
||||
static QColor FALLBACK_HIGHLIGHT_COLOR;
|
||||
|
||||
private:
|
||||
bool compare(const QString &id, const Badge &badge) const;
|
||||
|
||||
QString badgeName_;
|
||||
QString displayName_;
|
||||
bool hasAlert_;
|
||||
bool hasSound_;
|
||||
QUrl soundUrl_;
|
||||
std::shared_ptr<QColor> color_;
|
||||
|
||||
bool isMulti_;
|
||||
bool hasVersions_;
|
||||
QStringList badges_;
|
||||
};
|
||||
}; // namespace chatterino
|
||||
|
||||
namespace pajlada {
|
||||
|
||||
template <>
|
||||
struct Serialize<chatterino::HighlightBadge> {
|
||||
static rapidjson::Value get(const chatterino::HighlightBadge &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
rapidjson::Value ret(rapidjson::kObjectType);
|
||||
|
||||
chatterino::rj::set(ret, "name", value.badgeName(), a);
|
||||
chatterino::rj::set(ret, "displayName", value.displayName(), a);
|
||||
chatterino::rj::set(ret, "alert", value.hasAlert(), a);
|
||||
chatterino::rj::set(ret, "sound", value.hasSound(), a);
|
||||
chatterino::rj::set(ret, "soundUrl", value.getSoundUrl().toString(), a);
|
||||
chatterino::rj::set(ret, "color",
|
||||
value.getColor()->name(QColor::HexArgb), a);
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Deserialize<chatterino::HighlightBadge> {
|
||||
static chatterino::HighlightBadge get(const rapidjson::Value &value,
|
||||
bool *error)
|
||||
{
|
||||
if (!value.IsObject())
|
||||
{
|
||||
PAJLADA_REPORT_ERROR(error);
|
||||
return chatterino::HighlightBadge(QString(), QString(), false,
|
||||
false, "", QColor());
|
||||
}
|
||||
|
||||
QString _name;
|
||||
QString _displayName;
|
||||
bool _hasAlert = true;
|
||||
bool _hasSound = false;
|
||||
QString _soundUrl;
|
||||
QString encodedColor;
|
||||
|
||||
chatterino::rj::getSafe(value, "name", _name);
|
||||
chatterino::rj::getSafe(value, "displayName", _displayName);
|
||||
chatterino::rj::getSafe(value, "alert", _hasAlert);
|
||||
chatterino::rj::getSafe(value, "sound", _hasSound);
|
||||
chatterino::rj::getSafe(value, "soundUrl", _soundUrl);
|
||||
chatterino::rj::getSafe(value, "color", encodedColor);
|
||||
|
||||
auto _color = QColor(encodedColor);
|
||||
if (!_color.isValid())
|
||||
_color = chatterino::HighlightBadge::FALLBACK_HIGHLIGHT_COLOR;
|
||||
|
||||
return chatterino::HighlightBadge(_name, _displayName, _hasAlert,
|
||||
_hasSound, _soundUrl, _color);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace pajlada
|
||||
Reference in New Issue
Block a user