Better Highlights (#1320)
* Support for user-defined sounds and colors * Make color & sound columns selectable * Add custom row for subscription highlights * Add subscriptions to custom highlights and centrally manage highlight colors * Dynamically update message highlight colors
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
#include "providers/colors/ColorProvider.hpp"
|
||||
|
||||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
const ColorProvider &ColorProvider::instance()
|
||||
{
|
||||
static ColorProvider instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
ColorProvider::ColorProvider()
|
||||
: typeColorMap_()
|
||||
, defaultColors_()
|
||||
{
|
||||
this->initTypeColorMap();
|
||||
this->initDefaultColors();
|
||||
}
|
||||
|
||||
const std::shared_ptr<QColor> ColorProvider::color(ColorType type) const
|
||||
{
|
||||
return this->typeColorMap_.at(type);
|
||||
}
|
||||
|
||||
void ColorProvider::updateColor(ColorType type, QColor color)
|
||||
{
|
||||
auto colorPtr = this->typeColorMap_.at(type);
|
||||
*colorPtr = color;
|
||||
}
|
||||
|
||||
QSet<QColor> ColorProvider::recentColors() const
|
||||
{
|
||||
QSet<QColor> retVal;
|
||||
|
||||
/*
|
||||
* Currently, only colors used in highlight phrases are considered. This
|
||||
* may change at any point in the future.
|
||||
*/
|
||||
for (auto phrase : getApp()->highlights->phrases)
|
||||
{
|
||||
retVal.insert(*phrase.getColor());
|
||||
}
|
||||
|
||||
for (auto userHl : getApp()->highlights->highlightedUsers)
|
||||
{
|
||||
retVal.insert(*userHl.getColor());
|
||||
}
|
||||
|
||||
// Insert preset highlight colors
|
||||
retVal.insert(*this->color(ColorType::SelfHighlight));
|
||||
retVal.insert(*this->color(ColorType::Subscription));
|
||||
retVal.insert(*this->color(ColorType::Whisper));
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
const std::vector<QColor> &ColorProvider::defaultColors() const
|
||||
{
|
||||
return this->defaultColors_;
|
||||
}
|
||||
|
||||
void ColorProvider::initTypeColorMap()
|
||||
{
|
||||
// Read settings for custom highlight colors and save them in map.
|
||||
// If no custom values can be found, set up default values instead.
|
||||
auto backgrounds = getApp()->themes->messages.backgrounds;
|
||||
|
||||
QString customColor = getSettings()->selfHighlightColor;
|
||||
if (QColor(customColor).isValid())
|
||||
{
|
||||
this->typeColorMap_.insert(
|
||||
{ColorType::SelfHighlight, std::make_shared<QColor>(customColor)});
|
||||
}
|
||||
else
|
||||
{
|
||||
this->typeColorMap_.insert(
|
||||
{ColorType::SelfHighlight,
|
||||
std::make_shared<QColor>(backgrounds.highlighted)});
|
||||
}
|
||||
|
||||
customColor = getSettings()->subHighlightColor;
|
||||
if (QColor(customColor).isValid())
|
||||
{
|
||||
this->typeColorMap_.insert(
|
||||
{ColorType::Subscription, std::make_shared<QColor>(customColor)});
|
||||
}
|
||||
else
|
||||
{
|
||||
this->typeColorMap_.insert(
|
||||
{ColorType::Subscription,
|
||||
std::make_shared<QColor>(backgrounds.subscription)});
|
||||
}
|
||||
|
||||
customColor = getSettings()->whisperHighlightColor;
|
||||
if (QColor(customColor).isValid())
|
||||
{
|
||||
this->typeColorMap_.insert(
|
||||
{ColorType::Whisper, std::make_shared<QColor>(customColor)});
|
||||
}
|
||||
else
|
||||
{
|
||||
this->typeColorMap_.insert(
|
||||
{ColorType::Whisper,
|
||||
std::make_shared<QColor>(backgrounds.highlighted)});
|
||||
}
|
||||
}
|
||||
|
||||
void ColorProvider::initDefaultColors()
|
||||
{
|
||||
// Init default colors
|
||||
this->defaultColors_.emplace_back(31, 141, 43, 127); // Green-ish
|
||||
this->defaultColors_.emplace_back(28, 126, 141, 127); // Blue-ish
|
||||
this->defaultColors_.emplace_back(136, 141, 49, 127); // Golden-ish
|
||||
this->defaultColors_.emplace_back(143, 48, 24, 127); // Red-ish
|
||||
this->defaultColors_.emplace_back(28, 141, 117, 127); // Cyan-ish
|
||||
|
||||
auto backgrounds = getApp()->themes->messages.backgrounds;
|
||||
this->defaultColors_.push_back(backgrounds.highlighted);
|
||||
this->defaultColors_.push_back(backgrounds.subscription);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class ColorType { SelfHighlight, Subscription, Whisper };
|
||||
|
||||
class ColorProvider
|
||||
{
|
||||
public:
|
||||
static const ColorProvider &instance();
|
||||
|
||||
/**
|
||||
* @brief Return a std::shared_ptr to the color of the requested ColorType.
|
||||
*
|
||||
* If a custom color has been set for the requested ColorType, it is
|
||||
* returned. If no custom color exists for the type, a default color is
|
||||
* returned.
|
||||
*
|
||||
* We need to do this in order to be able to dynamically update the colors
|
||||
* of already parsed predefined (self highlights, subscriptions,
|
||||
* and whispers) highlights.
|
||||
*/
|
||||
const std::shared_ptr<QColor> color(ColorType type) const;
|
||||
|
||||
void updateColor(ColorType type, QColor color);
|
||||
|
||||
/**
|
||||
* @brief Return a set of recently used colors used anywhere in Chatterino.
|
||||
*/
|
||||
QSet<QColor> recentColors() const;
|
||||
|
||||
/**
|
||||
* @brief Return a vector of colors that are good defaults for use
|
||||
* throughout the program.
|
||||
*/
|
||||
const std::vector<QColor> &defaultColors() const;
|
||||
|
||||
private:
|
||||
ColorProvider();
|
||||
|
||||
void initTypeColorMap();
|
||||
void initDefaultColors();
|
||||
|
||||
std::unordered_map<ColorType, std::shared_ptr<QColor>> typeColorMap_;
|
||||
std::vector<QColor> defaultColors_;
|
||||
};
|
||||
} // namespace chatterino
|
||||
|
||||
// Adapted from Qt example: https://doc.qt.io/qt-5/qhash.html#qhash
|
||||
inline uint qHash(const QColor &key)
|
||||
{
|
||||
return qHash(key.name(QColor::HexArgb));
|
||||
}
|
||||
@@ -64,6 +64,25 @@ QColor getRandomColor(const QVariant &userId)
|
||||
return twitchUsernameColors[colorIndex];
|
||||
}
|
||||
|
||||
QUrl getFallbackHighlightSound()
|
||||
{
|
||||
using namespace chatterino;
|
||||
|
||||
QString path = getSettings()->pathHighlightSound;
|
||||
bool fileExists = QFileInfo::exists(path) && QFileInfo(path).isFile();
|
||||
|
||||
// Use fallback sound when checkbox is not checked
|
||||
// or custom file doesn't exist
|
||||
if (getSettings()->customHighlightSound && fileExists)
|
||||
{
|
||||
return QUrl::fromLocalFile(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
return QUrl("qrc:/sounds/ping2.wav");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino {
|
||||
@@ -233,17 +252,11 @@ void TwitchMessageBuilder::triggerHighlights()
|
||||
if (auto player = getPlayer())
|
||||
{
|
||||
// update the media player url if necessary
|
||||
QUrl highlightSoundUrl =
|
||||
getSettings()->customHighlightSound
|
||||
? QUrl::fromLocalFile(
|
||||
getSettings()->pathHighlightSound.getValue())
|
||||
: QUrl("qrc:/sounds/ping2.wav");
|
||||
|
||||
if (currentPlayerUrl != highlightSoundUrl)
|
||||
if (currentPlayerUrl != this->highlightSoundUrl_)
|
||||
{
|
||||
player->setMedia(highlightSoundUrl);
|
||||
player->setMedia(this->highlightSoundUrl_);
|
||||
|
||||
currentPlayerUrl = highlightSoundUrl;
|
||||
currentPlayerUrl = this->highlightSoundUrl_;
|
||||
}
|
||||
|
||||
player->play();
|
||||
@@ -967,6 +980,43 @@ void TwitchMessageBuilder::parseHighlights()
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
if (this->message().flags.has(MessageFlag::Subscription) &&
|
||||
getSettings()->enableSubHighlight)
|
||||
{
|
||||
if (getSettings()->enableSubHighlightTaskbar)
|
||||
{
|
||||
this->highlightAlert_ = true;
|
||||
}
|
||||
|
||||
if (getSettings()->enableSubHighlightSound)
|
||||
{
|
||||
this->highlightSound_ = true;
|
||||
|
||||
// Use custom sound if set, otherwise use fallback
|
||||
if (!getSettings()->subHighlightSoundUrl.getValue().isEmpty())
|
||||
{
|
||||
this->highlightSoundUrl_ =
|
||||
QUrl(getSettings()->subHighlightSoundUrl.getValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->highlightVisual_)
|
||||
{
|
||||
this->highlightVisual_ = true;
|
||||
this->message().flags.set(MessageFlag::Highlighted);
|
||||
this->message().highlightColor =
|
||||
ColorProvider::instance().color(ColorType::Subscription);
|
||||
}
|
||||
|
||||
// This message was a subscription.
|
||||
// Don't check for any other highlight phrases.
|
||||
return;
|
||||
}
|
||||
|
||||
auto currentUser = app->accounts->twitch.getCurrent();
|
||||
|
||||
QString currentUsername = currentUser->getUserName();
|
||||
@@ -993,23 +1043,33 @@ void TwitchMessageBuilder::parseHighlights()
|
||||
{
|
||||
this->highlightVisual_ = true;
|
||||
this->message().flags.set(MessageFlag::Highlighted);
|
||||
this->message().highlightColor = userHighlight.getColor();
|
||||
}
|
||||
|
||||
if (userHighlight.getAlert())
|
||||
if (userHighlight.hasAlert())
|
||||
{
|
||||
this->highlightAlert_ = true;
|
||||
}
|
||||
|
||||
if (userHighlight.getSound())
|
||||
if (userHighlight.hasSound())
|
||||
{
|
||||
this->highlightSound_ = true;
|
||||
// Use custom sound if set, otherwise use the fallback sound
|
||||
if (userHighlight.hasCustomSound())
|
||||
{
|
||||
this->highlightSoundUrl_ = userHighlight.getSoundUrl();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->highlightAlert_ && this->highlightSound_)
|
||||
{
|
||||
// Break if no further action can be taken from other
|
||||
// usernames Mostly used for regex stuff
|
||||
break;
|
||||
// Usernames "beat" highlight phrases: Once a username highlight
|
||||
// has been applied, no further highlight phrases will be checked
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1028,7 +1088,9 @@ void TwitchMessageBuilder::parseHighlights()
|
||||
{
|
||||
HighlightPhrase selfHighlight(
|
||||
currentUsername, getSettings()->enableSelfHighlightTaskbar,
|
||||
getSettings()->enableSelfHighlightSound, false, false);
|
||||
getSettings()->enableSelfHighlightSound, false, false,
|
||||
getSettings()->selfHighlightSoundUrl.getValue(),
|
||||
ColorProvider::instance().color(ColorType::SelfHighlight));
|
||||
activeHighlights.emplace_back(std::move(selfHighlight));
|
||||
}
|
||||
|
||||
@@ -1046,23 +1108,36 @@ void TwitchMessageBuilder::parseHighlights()
|
||||
{
|
||||
this->highlightVisual_ = true;
|
||||
this->message().flags.set(MessageFlag::Highlighted);
|
||||
this->message().highlightColor = highlight.getColor();
|
||||
}
|
||||
|
||||
if (highlight.getAlert())
|
||||
if (highlight.hasAlert())
|
||||
{
|
||||
this->highlightAlert_ = true;
|
||||
}
|
||||
|
||||
if (highlight.getSound())
|
||||
// Only set highlightSound_ if it hasn't been set by username
|
||||
// highlights already.
|
||||
if (highlight.hasSound() && !this->highlightSound_)
|
||||
{
|
||||
this->highlightSound_ = true;
|
||||
|
||||
// Use custom sound if set, otherwise use fallback sound
|
||||
if (highlight.hasCustomSound())
|
||||
{
|
||||
this->highlightSoundUrl_ = highlight.getSoundUrl();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->highlightAlert_ && this->highlightSound_)
|
||||
{
|
||||
// Break if no further action can be taken from other
|
||||
// highlights This might change if highlights can have
|
||||
// custom colors/sounds/actions
|
||||
// Break once the first highlight has been set. If a message would
|
||||
// trigger multiple highlights, only the first one from the list
|
||||
// will be applied.
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1077,6 +1152,24 @@ void TwitchMessageBuilder::parseHighlights()
|
||||
if (getSettings()->enableWhisperHighlightSound)
|
||||
{
|
||||
this->highlightSound_ = true;
|
||||
|
||||
// Use custom sound if set, otherwise use fallback
|
||||
if (!getSettings()->whisperHighlightSoundUrl.getValue().isEmpty())
|
||||
{
|
||||
this->highlightSoundUrl_ =
|
||||
QUrl(getSettings()->whisperHighlightSoundUrl.getValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
||||
}
|
||||
}
|
||||
if (!this->highlightVisual_)
|
||||
{
|
||||
this->highlightVisual_ = true;
|
||||
this->message().flags.set(MessageFlag::Highlighted);
|
||||
this->message().highlightColor =
|
||||
ColorProvider::instance().color(ColorType::Whisper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,8 @@ private:
|
||||
bool highlightVisual_ = false;
|
||||
bool highlightAlert_ = false;
|
||||
bool highlightSound_ = false;
|
||||
|
||||
QUrl highlightSoundUrl_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
Reference in New Issue
Block a user