mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
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:
parent
00414eb779
commit
5957551d06
29 changed files with 1822 additions and 187 deletions
|
@ -133,6 +133,7 @@ SOURCES += \
|
|||
src/controllers/highlights/HighlightBlacklistModel.cpp \
|
||||
src/controllers/highlights/HighlightController.cpp \
|
||||
src/controllers/highlights/HighlightModel.cpp \
|
||||
src/controllers/highlights/HighlightPhrase.cpp \
|
||||
src/controllers/highlights/UserHighlightModel.cpp \
|
||||
src/controllers/ignores/IgnoreController.cpp \
|
||||
src/controllers/ignores/IgnoreModel.cpp \
|
||||
|
@ -166,6 +167,7 @@ SOURCES += \
|
|||
src/providers/bttv/BttvEmotes.cpp \
|
||||
src/providers/bttv/LoadBttvChannelEmote.cpp \
|
||||
src/providers/chatterino/ChatterinoBadges.cpp \
|
||||
src/providers/colors/ColorProvider.cpp \
|
||||
src/providers/emoji/Emojis.cpp \
|
||||
src/providers/ffz/FfzEmotes.cpp \
|
||||
src/providers/irc/AbstractIrcServer.cpp \
|
||||
|
@ -228,6 +230,7 @@ SOURCES += \
|
|||
src/widgets/BasePopup.cpp \
|
||||
src/widgets/BaseWidget.cpp \
|
||||
src/widgets/BaseWindow.cpp \
|
||||
src/widgets/dialogs/ColorPickerDialog.cpp \
|
||||
src/widgets/dialogs/EmotePopup.cpp \
|
||||
src/widgets/dialogs/IrcConnectionEditor.cpp \
|
||||
src/widgets/dialogs/LastRunCrashDialog.cpp \
|
||||
|
@ -243,12 +246,14 @@ SOURCES += \
|
|||
src/widgets/dialogs/WelcomeDialog.cpp \
|
||||
src/widgets/helper/Button.cpp \
|
||||
src/widgets/helper/ChannelView.cpp \
|
||||
src/widgets/helper/ColorButton.cpp \
|
||||
src/widgets/helper/ComboBoxItemDelegate.cpp \
|
||||
src/widgets/helper/DebugPopup.cpp \
|
||||
src/widgets/helper/EditableModelView.cpp \
|
||||
src/widgets/helper/EffectLabel.cpp \
|
||||
src/widgets/helper/NotebookButton.cpp \
|
||||
src/widgets/helper/NotebookTab.cpp \
|
||||
src/widgets/helper/QColorPicker.cpp \
|
||||
src/widgets/helper/ResizingTextEdit.cpp \
|
||||
src/widgets/helper/ScrollbarHighlight.cpp \
|
||||
src/widgets/helper/SearchPopup.cpp \
|
||||
|
@ -366,6 +371,7 @@ HEADERS += \
|
|||
src/providers/bttv/BttvEmotes.hpp \
|
||||
src/providers/bttv/LoadBttvChannelEmote.hpp \
|
||||
src/providers/chatterino/ChatterinoBadges.hpp \
|
||||
src/providers/colors/ColorProvider.hpp \
|
||||
src/providers/emoji/Emojis.hpp \
|
||||
src/providers/ffz/FfzEmotes.hpp \
|
||||
src/providers/irc/AbstractIrcServer.hpp \
|
||||
|
@ -450,6 +456,7 @@ HEADERS += \
|
|||
src/widgets/BasePopup.hpp \
|
||||
src/widgets/BaseWidget.hpp \
|
||||
src/widgets/BaseWindow.hpp \
|
||||
src/widgets/dialogs/ColorPickerDialog.hpp \
|
||||
src/widgets/dialogs/EmotePopup.hpp \
|
||||
src/widgets/dialogs/IrcConnectionEditor.hpp \
|
||||
src/widgets/dialogs/LastRunCrashDialog.hpp \
|
||||
|
@ -465,6 +472,7 @@ HEADERS += \
|
|||
src/widgets/dialogs/WelcomeDialog.hpp \
|
||||
src/widgets/helper/Button.hpp \
|
||||
src/widgets/helper/ChannelView.hpp \
|
||||
src/widgets/helper/ColorButton.hpp \
|
||||
src/widgets/helper/ComboBoxItemDelegate.hpp \
|
||||
src/widgets/helper/CommonTexts.hpp \
|
||||
src/widgets/helper/DebugPopup.hpp \
|
||||
|
@ -473,6 +481,7 @@ HEADERS += \
|
|||
src/widgets/helper/Line.hpp \
|
||||
src/widgets/helper/NotebookButton.hpp \
|
||||
src/widgets/helper/NotebookTab.hpp \
|
||||
src/widgets/helper/QColorPicker.hpp \
|
||||
src/widgets/helper/ResizingTextEdit.hpp \
|
||||
src/widgets/helper/ScrollbarHighlight.hpp \
|
||||
src/widgets/helper/SearchPopup.hpp \
|
||||
|
|
|
@ -18,16 +18,17 @@ HighlightBlacklistUser HighlightBlacklistModel::getItemFromRow(
|
|||
{
|
||||
// key, regex
|
||||
|
||||
return HighlightBlacklistUser{row[0]->data(Qt::DisplayRole).toString(),
|
||||
row[1]->data(Qt::CheckStateRole).toBool()};
|
||||
return HighlightBlacklistUser{
|
||||
row[Column::Pattern]->data(Qt::DisplayRole).toString(),
|
||||
row[Column::UseRegex]->data(Qt::CheckStateRole).toBool()};
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void HighlightBlacklistModel::getRowFromItem(const HighlightBlacklistUser &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.getPattern());
|
||||
setBoolItem(row[1], item.isRegex());
|
||||
setStringItem(row[Column::Pattern], item.getPattern());
|
||||
setBoolItem(row[Column::UseRegex], item.isRegex());
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -13,6 +13,12 @@ class HighlightBlacklistModel : public SignalVectorModel<HighlightBlacklistUser>
|
|||
{
|
||||
explicit HighlightBlacklistModel(QObject *parent);
|
||||
|
||||
public:
|
||||
enum Column {
|
||||
Pattern = 0,
|
||||
UseRegex = 1,
|
||||
};
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual HighlightBlacklistUser getItemFromRow(
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace chatterino {
|
|||
|
||||
// commandmodel
|
||||
HighlightModel::HighlightModel(QObject *parent)
|
||||
: SignalVectorModel<HighlightPhrase>(5, parent)
|
||||
: SignalVectorModel<HighlightPhrase>(7, parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -16,52 +16,103 @@ HighlightModel::HighlightModel(QObject *parent)
|
|||
HighlightPhrase HighlightModel::getItemFromRow(
|
||||
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
||||
{
|
||||
// key, alert, sound, regex, case-sensitivity
|
||||
// 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 HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
|
||||
row[1]->data(Qt::CheckStateRole).toBool(),
|
||||
row[2]->data(Qt::CheckStateRole).toBool(),
|
||||
row[3]->data(Qt::CheckStateRole).toBool(),
|
||||
row[4]->data(Qt::CheckStateRole).toBool()};
|
||||
return HighlightPhrase{
|
||||
row[Column::Pattern]->data(Qt::DisplayRole).toString(),
|
||||
row[Column::FlashTaskbar]->data(Qt::CheckStateRole).toBool(),
|
||||
row[Column::PlaySound]->data(Qt::CheckStateRole).toBool(),
|
||||
row[Column::UseRegex]->data(Qt::CheckStateRole).toBool(),
|
||||
row[Column::CaseSensitive]->data(Qt::CheckStateRole).toBool(),
|
||||
row[Column::SoundPath]->data(Qt::UserRole).toString(),
|
||||
highlightColor};
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void HighlightModel::getRowFromItem(const HighlightPhrase &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.getPattern());
|
||||
setBoolItem(row[1], item.getAlert());
|
||||
setBoolItem(row[2], item.getSound());
|
||||
setBoolItem(row[3], item.isRegex());
|
||||
setBoolItem(row[4], item.isCaseSensitive());
|
||||
setStringItem(row[Column::Pattern], item.getPattern());
|
||||
setBoolItem(row[Column::FlashTaskbar], item.hasAlert());
|
||||
setBoolItem(row[Column::PlaySound], item.hasSound());
|
||||
setBoolItem(row[Column::UseRegex], item.isRegex());
|
||||
setBoolItem(row[Column::CaseSensitive], item.isCaseSensitive());
|
||||
setFilePathItem(row[Column::SoundPath], item.getSoundUrl());
|
||||
setColorItem(row[Column::Color], *item.getColor());
|
||||
}
|
||||
|
||||
void HighlightModel::afterInit()
|
||||
{
|
||||
// Highlight settings for own username
|
||||
std::vector<QStandardItem *> usernameRow = this->createRow();
|
||||
setBoolItem(usernameRow[0], getSettings()->enableSelfHighlight.getValue(),
|
||||
true, false);
|
||||
usernameRow[0]->setData("Your username (automatic)", Qt::DisplayRole);
|
||||
setBoolItem(usernameRow[1],
|
||||
setBoolItem(usernameRow[Column::Pattern],
|
||||
getSettings()->enableSelfHighlight.getValue(), true, false);
|
||||
usernameRow[Column::Pattern]->setData("Your username (automatic)",
|
||||
Qt::DisplayRole);
|
||||
setBoolItem(usernameRow[Column::FlashTaskbar],
|
||||
getSettings()->enableSelfHighlightTaskbar.getValue(), true,
|
||||
false);
|
||||
setBoolItem(usernameRow[2],
|
||||
setBoolItem(usernameRow[Column::PlaySound],
|
||||
getSettings()->enableSelfHighlightSound.getValue(), true,
|
||||
false);
|
||||
usernameRow[3]->setFlags(0);
|
||||
usernameRow[Column::UseRegex]->setFlags(0);
|
||||
usernameRow[Column::CaseSensitive]->setFlags(0);
|
||||
|
||||
QUrl selfSound = QUrl(getSettings()->selfHighlightSoundUrl.getValue());
|
||||
setFilePathItem(usernameRow[Column::SoundPath], selfSound);
|
||||
|
||||
auto selfColor = ColorProvider::instance().color(ColorType::SelfHighlight);
|
||||
setColorItem(usernameRow[Column::Color], *selfColor);
|
||||
|
||||
this->insertCustomRow(usernameRow, 0);
|
||||
|
||||
// Highlight settings for whispers
|
||||
std::vector<QStandardItem *> whisperRow = this->createRow();
|
||||
setBoolItem(whisperRow[0], getSettings()->enableWhisperHighlight.getValue(),
|
||||
true, false);
|
||||
whisperRow[0]->setData("Whispers", Qt::DisplayRole);
|
||||
setBoolItem(whisperRow[1],
|
||||
setBoolItem(whisperRow[Column::Pattern],
|
||||
getSettings()->enableWhisperHighlight.getValue(), true, false);
|
||||
whisperRow[Column::Pattern]->setData("Whispers", Qt::DisplayRole);
|
||||
setBoolItem(whisperRow[Column::FlashTaskbar],
|
||||
getSettings()->enableWhisperHighlightTaskbar.getValue(), true,
|
||||
false);
|
||||
setBoolItem(whisperRow[2],
|
||||
setBoolItem(whisperRow[Column::PlaySound],
|
||||
getSettings()->enableWhisperHighlightSound.getValue(), true,
|
||||
false);
|
||||
whisperRow[3]->setFlags(0);
|
||||
whisperRow[Column::UseRegex]->setFlags(0);
|
||||
whisperRow[Column::CaseSensitive]->setFlags(0);
|
||||
|
||||
QUrl whisperSound =
|
||||
QUrl(getSettings()->whisperHighlightSoundUrl.getValue());
|
||||
setFilePathItem(whisperRow[Column::SoundPath], whisperSound);
|
||||
|
||||
auto whisperColor = ColorProvider::instance().color(ColorType::Whisper);
|
||||
setColorItem(whisperRow[Column::Color], *whisperColor);
|
||||
|
||||
this->insertCustomRow(whisperRow, 1);
|
||||
|
||||
// Highlight settings for subscription messages
|
||||
std::vector<QStandardItem *> subRow = this->createRow();
|
||||
setBoolItem(subRow[Column::Pattern],
|
||||
getSettings()->enableSubHighlight.getValue(), true, false);
|
||||
subRow[Column::Pattern]->setData("Subscriptions", Qt::DisplayRole);
|
||||
setBoolItem(subRow[Column::FlashTaskbar],
|
||||
getSettings()->enableSubHighlightTaskbar.getValue(), true,
|
||||
false);
|
||||
setBoolItem(subRow[Column::PlaySound],
|
||||
getSettings()->enableSubHighlightSound.getValue(), true, false);
|
||||
subRow[Column::UseRegex]->setFlags(0);
|
||||
subRow[Column::CaseSensitive]->setFlags(0);
|
||||
|
||||
QUrl subSound = QUrl(getSettings()->subHighlightSoundUrl.getValue());
|
||||
setFilePathItem(subRow[Column::SoundPath], subSound);
|
||||
|
||||
auto subColor = ColorProvider::instance().color(ColorType::Subscription);
|
||||
setColorItem(subRow[Column::Color], *subColor);
|
||||
|
||||
this->insertCustomRow(subRow, 2);
|
||||
}
|
||||
|
||||
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
|
@ -70,7 +121,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
{
|
||||
switch (column)
|
||||
{
|
||||
case 0: {
|
||||
case Column::Pattern: {
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
|
@ -82,10 +133,14 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
getSettings()->enableWhisperHighlight.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == 2)
|
||||
{
|
||||
getSettings()->enableSubHighlight.setValue(value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1: {
|
||||
case Column::FlashTaskbar: {
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
|
@ -98,10 +153,15 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
getSettings()->enableWhisperHighlightTaskbar.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == 2)
|
||||
{
|
||||
getSettings()->enableSubHighlightTaskbar.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2: {
|
||||
case Column::PlaySound: {
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
|
@ -114,11 +174,62 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
getSettings()->enableWhisperHighlightSound.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == 2)
|
||||
{
|
||||
getSettings()->enableSubHighlightSound.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3: {
|
||||
// empty element
|
||||
case Column::UseRegex: {
|
||||
// Regex --> empty
|
||||
}
|
||||
break;
|
||||
case Column::CaseSensitive: {
|
||||
// Case-sensitivity --> empty
|
||||
}
|
||||
break;
|
||||
case Column::SoundPath: {
|
||||
// Custom sound file
|
||||
if (role == Qt::UserRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
{
|
||||
getSettings()->selfHighlightSoundUrl.setValue(
|
||||
value.toString());
|
||||
}
|
||||
else if (rowIndex == 1)
|
||||
{
|
||||
getSettings()->whisperHighlightSoundUrl.setValue(
|
||||
value.toString());
|
||||
}
|
||||
else if (rowIndex == 2)
|
||||
{
|
||||
getSettings()->subHighlightSoundUrl.setValue(
|
||||
value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Column::Color: {
|
||||
// Custom color
|
||||
if (role == Qt::DecorationRole)
|
||||
{
|
||||
auto colorName = value.value<QColor>().name(QColor::HexArgb);
|
||||
if (rowIndex == 0)
|
||||
{
|
||||
getSettings()->selfHighlightColor.setValue(colorName);
|
||||
}
|
||||
else if (rowIndex == 1)
|
||||
{
|
||||
getSettings()->whisperHighlightColor.setValue(colorName);
|
||||
}
|
||||
else if (rowIndex == 2)
|
||||
{
|
||||
getSettings()->subHighlightColor.setValue(colorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,18 @@ class HighlightModel : public SignalVectorModel<HighlightPhrase>
|
|||
{
|
||||
explicit HighlightModel(QObject *parent);
|
||||
|
||||
public:
|
||||
// Used here, in HighlightingPage and in UserHighlightModel
|
||||
enum Column {
|
||||
Pattern = 0,
|
||||
FlashTaskbar = 1,
|
||||
PlaySound = 2,
|
||||
UseRegex = 3,
|
||||
CaseSensitive = 4,
|
||||
SoundPath = 5,
|
||||
Color = 6
|
||||
};
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual HighlightPhrase getItemFromRow(
|
||||
|
|
113
src/controllers/highlights/HighlightPhrase.cpp
Normal file
113
src/controllers/highlights/HighlightPhrase.cpp
Normal file
|
@ -0,0 +1,113 @@
|
|||
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
bool HighlightPhrase::operator==(const HighlightPhrase &other) const
|
||||
{
|
||||
return std::tie(this->pattern_, this->hasSound_, this->hasAlert_,
|
||||
this->isRegex_, this->isCaseSensitive_, this->soundUrl_,
|
||||
this->color_) == std::tie(other.pattern_, other.hasSound_,
|
||||
other.hasAlert_, other.isRegex_,
|
||||
other.isCaseSensitive_,
|
||||
other.soundUrl_, other.color_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a new HighlightPhrase.
|
||||
*
|
||||
* Use this constructor when updating an existing HighlightPhrase's color.
|
||||
*/
|
||||
HighlightPhrase::HighlightPhrase(const QString &pattern, bool hasAlert,
|
||||
bool hasSound, bool isRegex,
|
||||
bool isCaseSensitive, const QString &soundUrl,
|
||||
QColor color)
|
||||
: pattern_(pattern)
|
||||
, hasAlert_(hasAlert)
|
||||
, hasSound_(hasSound)
|
||||
, isRegex_(isRegex)
|
||||
, isCaseSensitive_(isCaseSensitive)
|
||||
, soundUrl_(soundUrl)
|
||||
, regex_(isRegex_ ? pattern
|
||||
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
||||
QRegularExpression::UseUnicodePropertiesOption |
|
||||
(isCaseSensitive_ ? QRegularExpression::NoPatternOption
|
||||
: QRegularExpression::CaseInsensitiveOption))
|
||||
{
|
||||
this->color_ = std::make_shared<QColor>(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a new HighlightPhrase.
|
||||
*
|
||||
* Use this constructor when creating a new HighlightPhrase.
|
||||
*/
|
||||
HighlightPhrase::HighlightPhrase(const QString &pattern, bool hasAlert,
|
||||
bool hasSound, bool isRegex,
|
||||
bool isCaseSensitive, const QString &soundUrl,
|
||||
std::shared_ptr<QColor> color)
|
||||
: pattern_(pattern)
|
||||
, hasAlert_(hasAlert)
|
||||
, hasSound_(hasSound)
|
||||
, isRegex_(isRegex)
|
||||
, isCaseSensitive_(isCaseSensitive)
|
||||
, soundUrl_(soundUrl)
|
||||
, color_(color)
|
||||
, regex_(isRegex_ ? pattern
|
||||
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
||||
QRegularExpression::UseUnicodePropertiesOption |
|
||||
(isCaseSensitive_ ? QRegularExpression::NoPatternOption
|
||||
: QRegularExpression::CaseInsensitiveOption))
|
||||
{
|
||||
}
|
||||
|
||||
const QString &HighlightPhrase::getPattern() const
|
||||
{
|
||||
return this->pattern_;
|
||||
}
|
||||
|
||||
bool HighlightPhrase::hasAlert() const
|
||||
{
|
||||
return this->hasAlert_;
|
||||
}
|
||||
|
||||
bool HighlightPhrase::hasSound() const
|
||||
{
|
||||
return this->hasSound_;
|
||||
}
|
||||
|
||||
bool HighlightPhrase::hasCustomSound() const
|
||||
{
|
||||
return !this->soundUrl_.isEmpty();
|
||||
}
|
||||
|
||||
bool HighlightPhrase::isRegex() const
|
||||
{
|
||||
return this->isRegex_;
|
||||
}
|
||||
|
||||
bool HighlightPhrase::isValid() const
|
||||
{
|
||||
return !this->pattern_.isEmpty() && this->regex_.isValid();
|
||||
}
|
||||
|
||||
bool HighlightPhrase::isMatch(const QString &subject) const
|
||||
{
|
||||
return this->isValid() && this->regex_.match(subject).hasMatch();
|
||||
}
|
||||
|
||||
bool HighlightPhrase::isCaseSensitive() const
|
||||
{
|
||||
return this->isCaseSensitive_;
|
||||
}
|
||||
|
||||
const QUrl &HighlightPhrase::getSoundUrl() const
|
||||
{
|
||||
return this->soundUrl_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<QColor> HighlightPhrase::getColor() const
|
||||
{
|
||||
return this->color_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "providers/colors/ColorProvider.hpp"
|
||||
#include "util/RapidJsonSerializeQString.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
|
@ -12,70 +13,64 @@ namespace chatterino {
|
|||
class HighlightPhrase
|
||||
{
|
||||
public:
|
||||
bool operator==(const HighlightPhrase &other) const
|
||||
{
|
||||
return std::tie(this->pattern_, this->sound_, this->alert_,
|
||||
this->isRegex_, this->caseSensitive_) ==
|
||||
std::tie(other.pattern_, other.sound_, other.alert_,
|
||||
other.isRegex_, other.caseSensitive_);
|
||||
}
|
||||
bool operator==(const HighlightPhrase &other) const;
|
||||
|
||||
HighlightPhrase(const QString &pattern, bool alert, bool sound,
|
||||
bool isRegex, bool caseSensitive)
|
||||
: pattern_(pattern)
|
||||
, alert_(alert)
|
||||
, sound_(sound)
|
||||
, isRegex_(isRegex)
|
||||
, caseSensitive_(caseSensitive)
|
||||
, regex_(
|
||||
isRegex_ ? pattern
|
||||
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
||||
QRegularExpression::UseUnicodePropertiesOption |
|
||||
(caseSensitive_ ? QRegularExpression::NoPatternOption
|
||||
: QRegularExpression::CaseInsensitiveOption))
|
||||
{
|
||||
}
|
||||
HighlightPhrase(const QString &pattern, bool hasAlert, bool hasSound,
|
||||
bool isRegex, bool isCaseSensitive, const QString &soundUrl,
|
||||
QColor color);
|
||||
|
||||
const QString &getPattern() const
|
||||
{
|
||||
return this->pattern_;
|
||||
}
|
||||
bool getAlert() const
|
||||
{
|
||||
return this->alert_;
|
||||
}
|
||||
bool getSound() const
|
||||
{
|
||||
return this->sound_;
|
||||
}
|
||||
bool isRegex() const
|
||||
{
|
||||
return this->isRegex_;
|
||||
}
|
||||
HighlightPhrase(const QString &pattern, bool hasAlert, bool hasSound,
|
||||
bool isRegex, bool isCaseSensitive, const QString &soundUrl,
|
||||
std::shared_ptr<QColor> color);
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !this->pattern_.isEmpty() && this->regex_.isValid();
|
||||
}
|
||||
const QString &getPattern() const;
|
||||
bool hasAlert() const;
|
||||
|
||||
bool isMatch(const QString &subject) const
|
||||
{
|
||||
return this->isValid() && this->regex_.match(subject).hasMatch();
|
||||
}
|
||||
/**
|
||||
* @brief Check if this highlight phrase should play a sound when
|
||||
* triggered.
|
||||
*
|
||||
* In distinction from `HighlightPhrase::hasCustomSound`, this method only
|
||||
* checks whether or not ANY sound should be played when the phrase is
|
||||
* triggered.
|
||||
*
|
||||
* To check whether a custom sound is set, use
|
||||
* `HighlightPhrase::hasCustomSound` instead.
|
||||
*
|
||||
* @return true, if this highlight phrase should play a sound when
|
||||
* triggered, false otherwise
|
||||
*/
|
||||
bool hasSound() const;
|
||||
|
||||
bool isCaseSensitive() const
|
||||
{
|
||||
return this->caseSensitive_;
|
||||
}
|
||||
/**
|
||||
* @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;
|
||||
|
||||
bool isRegex() const;
|
||||
bool isValid() const;
|
||||
bool isMatch(const QString &subject) const;
|
||||
bool isCaseSensitive() const;
|
||||
const QUrl &getSoundUrl() const;
|
||||
const std::shared_ptr<QColor> getColor() const;
|
||||
|
||||
private:
|
||||
QString pattern_;
|
||||
bool alert_;
|
||||
bool sound_;
|
||||
bool hasAlert_;
|
||||
bool hasSound_;
|
||||
bool isRegex_;
|
||||
bool caseSensitive_;
|
||||
bool isCaseSensitive_;
|
||||
QUrl soundUrl_;
|
||||
std::shared_ptr<QColor> color_;
|
||||
QRegularExpression regex_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace pajlada {
|
||||
|
@ -88,10 +83,13 @@ struct Serialize<chatterino::HighlightPhrase> {
|
|||
rapidjson::Value ret(rapidjson::kObjectType);
|
||||
|
||||
chatterino::rj::set(ret, "pattern", value.getPattern(), a);
|
||||
chatterino::rj::set(ret, "alert", value.getAlert(), a);
|
||||
chatterino::rj::set(ret, "sound", value.getSound(), a);
|
||||
chatterino::rj::set(ret, "alert", value.hasAlert(), a);
|
||||
chatterino::rj::set(ret, "sound", value.hasSound(), a);
|
||||
chatterino::rj::set(ret, "regex", value.isRegex(), a);
|
||||
chatterino::rj::set(ret, "case", value.isCaseSensitive(), a);
|
||||
chatterino::rj::set(ret, "soundUrl", value.getSoundUrl().toString(), a);
|
||||
chatterino::rj::set(ret, "color",
|
||||
value.getColor()->name(QColor::HexArgb), a);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -104,23 +102,30 @@ struct Deserialize<chatterino::HighlightPhrase> {
|
|||
if (!value.IsObject())
|
||||
{
|
||||
return chatterino::HighlightPhrase(QString(), true, false, false,
|
||||
false);
|
||||
false, "", QColor());
|
||||
}
|
||||
|
||||
QString _pattern;
|
||||
bool _alert = true;
|
||||
bool _sound = false;
|
||||
bool _hasAlert = true;
|
||||
bool _hasSound = false;
|
||||
bool _isRegex = false;
|
||||
bool _caseSensitive = false;
|
||||
bool _isCaseSensitive = false;
|
||||
QString _soundUrl;
|
||||
QString encodedColor;
|
||||
|
||||
chatterino::rj::getSafe(value, "pattern", _pattern);
|
||||
chatterino::rj::getSafe(value, "alert", _alert);
|
||||
chatterino::rj::getSafe(value, "sound", _sound);
|
||||
chatterino::rj::getSafe(value, "alert", _hasAlert);
|
||||
chatterino::rj::getSafe(value, "sound", _hasSound);
|
||||
chatterino::rj::getSafe(value, "regex", _isRegex);
|
||||
chatterino::rj::getSafe(value, "case", _caseSensitive);
|
||||
chatterino::rj::getSafe(value, "case", _isCaseSensitive);
|
||||
chatterino::rj::getSafe(value, "soundUrl", _soundUrl);
|
||||
chatterino::rj::getSafe(value, "color", encodedColor);
|
||||
|
||||
return chatterino::HighlightPhrase(_pattern, _alert, _sound, _isRegex,
|
||||
_caseSensitive);
|
||||
auto _color = QColor(encodedColor);
|
||||
|
||||
return chatterino::HighlightPhrase(_pattern, _hasAlert, _hasSound,
|
||||
_isRegex, _isCaseSensitive,
|
||||
_soundUrl, _color);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "UserHighlightModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/highlights/HighlightModel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
|
@ -8,7 +9,7 @@ namespace chatterino {
|
|||
|
||||
// commandmodel
|
||||
UserHighlightModel::UserHighlightModel(QObject *parent)
|
||||
: SignalVectorModel<HighlightPhrase>(5, parent)
|
||||
: SignalVectorModel<HighlightPhrase>(7, parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -16,24 +17,37 @@ UserHighlightModel::UserHighlightModel(QObject *parent)
|
|||
HighlightPhrase UserHighlightModel::getItemFromRow(
|
||||
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
||||
{
|
||||
// key, regex
|
||||
using Column = HighlightModel::Column;
|
||||
|
||||
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
|
||||
row[1]->data(Qt::CheckStateRole).toBool(),
|
||||
row[2]->data(Qt::CheckStateRole).toBool(),
|
||||
row[3]->data(Qt::CheckStateRole).toBool(),
|
||||
row[4]->data(Qt::CheckStateRole).toBool()};
|
||||
// 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 HighlightPhrase{
|
||||
row[Column::Pattern]->data(Qt::DisplayRole).toString(),
|
||||
row[Column::FlashTaskbar]->data(Qt::CheckStateRole).toBool(),
|
||||
row[Column::PlaySound]->data(Qt::CheckStateRole).toBool(),
|
||||
row[Column::UseRegex]->data(Qt::CheckStateRole).toBool(),
|
||||
row[Column::CaseSensitive]->data(Qt::CheckStateRole).toBool(),
|
||||
row[Column::SoundPath]->data(Qt::UserRole).toString(),
|
||||
highlightColor};
|
||||
}
|
||||
|
||||
// row into vector item
|
||||
void UserHighlightModel::getRowFromItem(const HighlightPhrase &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.getPattern());
|
||||
setBoolItem(row[1], item.getAlert());
|
||||
setBoolItem(row[2], item.getSound());
|
||||
setBoolItem(row[3], item.isRegex());
|
||||
setBoolItem(row[4], item.isCaseSensitive());
|
||||
using Column = HighlightModel::Column;
|
||||
|
||||
setStringItem(row[Column::Pattern], item.getPattern());
|
||||
setBoolItem(row[Column::FlashTaskbar], item.hasAlert());
|
||||
setBoolItem(row[Column::PlaySound], item.hasSound());
|
||||
setBoolItem(row[Column::UseRegex], item.isRegex());
|
||||
setBoolItem(row[Column::CaseSensitive], item.isCaseSensitive());
|
||||
setFilePathItem(row[Column::SoundPath], item.getSoundUrl());
|
||||
setColorItem(row[Column::Color], *item.getColor());
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
#include "messages/Message.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "MessageElement.hpp"
|
||||
#include "providers/twitch/PubsubActions.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
#include "util/IrcHelpers.hpp"
|
||||
|
||||
|
@ -24,11 +27,13 @@ SBHighlight Message::getScrollBarHighlight() const
|
|||
if (this->flags.has(MessageFlag::Highlighted) ||
|
||||
this->flags.has(MessageFlag::HighlightedWhisper))
|
||||
{
|
||||
return SBHighlight(SBHighlight::Highlight);
|
||||
return SBHighlight(this->highlightColor);
|
||||
}
|
||||
else if (this->flags.has(MessageFlag::Subscription))
|
||||
else if (this->flags.has(MessageFlag::Subscription) &&
|
||||
getSettings()->enableSubHighlight)
|
||||
{
|
||||
return SBHighlight(SBHighlight::Subscription);
|
||||
return SBHighlight(
|
||||
ColorProvider::instance().color(ColorType::Subscription));
|
||||
}
|
||||
return SBHighlight();
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ struct Message : boost::noncopyable {
|
|||
QString displayName;
|
||||
QString localizedName;
|
||||
QString timeoutUser;
|
||||
std::shared_ptr<QColor> highlightColor;
|
||||
uint32_t count = 1;
|
||||
std::vector<std::unique_ptr<MessageElement>> elements;
|
||||
|
||||
|
|
|
@ -25,6 +25,19 @@
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
QColor blendColors(const QColor &base, const QColor &apply)
|
||||
{
|
||||
const qreal &alpha = apply.alphaF();
|
||||
QColor result;
|
||||
result.setRgbF(base.redF() * (1 - alpha) + apply.redF() * alpha,
|
||||
base.greenF() * (1 - alpha) + apply.greenF() * alpha,
|
||||
base.blueF() * (1 - alpha) + apply.blueF() * alpha);
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
MessageLayout::MessageLayout(MessagePtr message)
|
||||
: message_(message)
|
||||
, container_(std::make_shared<MessageLayoutContainer>())
|
||||
|
@ -249,21 +262,33 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
|
|||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
|
||||
// draw background
|
||||
QColor backgroundColor = app->themes->messages.backgrounds.regular;
|
||||
QColor backgroundColor = [this, &app] {
|
||||
if (getSettings()->alternateMessages.getValue() &&
|
||||
this->flags.has(MessageLayoutFlag::AlternateBackground))
|
||||
{
|
||||
return app->themes->messages.backgrounds.alternate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return app->themes->messages.backgrounds.regular;
|
||||
}
|
||||
}();
|
||||
|
||||
if ((this->message_->flags.has(MessageFlag::Highlighted) ||
|
||||
this->message_->flags.has(MessageFlag::HighlightedWhisper)) &&
|
||||
!this->flags.has(MessageLayoutFlag::IgnoreHighlights))
|
||||
{
|
||||
backgroundColor = app->themes->messages.backgrounds.highlighted;
|
||||
// Blend highlight color with usual background color
|
||||
backgroundColor =
|
||||
blendColors(backgroundColor, *this->message_->highlightColor);
|
||||
}
|
||||
else if (this->message_->flags.has(MessageFlag::Subscription))
|
||||
else if (this->message_->flags.has(MessageFlag::Subscription) &&
|
||||
getSettings()->enableSubHighlight)
|
||||
{
|
||||
backgroundColor = app->themes->messages.backgrounds.subscription;
|
||||
}
|
||||
else if (getSettings()->alternateMessages.getValue() &&
|
||||
this->flags.has(MessageLayoutFlag::AlternateBackground))
|
||||
{
|
||||
backgroundColor = app->themes->messages.backgrounds.alternate;
|
||||
// Blend highlight color with usual background color
|
||||
backgroundColor = blendColors(
|
||||
backgroundColor,
|
||||
*ColorProvider::instance().color(ColorType::Subscription));
|
||||
}
|
||||
else if (this->message_->flags.has(MessageFlag::AutoMod))
|
||||
{
|
||||
|
|
124
src/providers/colors/ColorProvider.cpp
Normal file
124
src/providers/colors/ColorProvider.cpp
Normal file
|
@ -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
|
53
src/providers/colors/ColorProvider.hpp
Normal file
53
src/providers/colors/ColorProvider.hpp
Normal file
|
@ -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
|
||||
|
|
|
@ -146,18 +146,39 @@ public:
|
|||
/// Highlighting
|
||||
// BoolSetting enableHighlights = {"/highlighting/enabled", true};
|
||||
BoolSetting customHighlightSound = {"/highlighting/useCustomSound", false};
|
||||
|
||||
BoolSetting enableSelfHighlight = {
|
||||
"/highlighting/selfHighlight/nameIsHighlightKeyword", true};
|
||||
BoolSetting enableSelfHighlightSound = {
|
||||
"/highlighting/selfHighlight/enableSound", true};
|
||||
BoolSetting enableSelfHighlightTaskbar = {
|
||||
"/highlighting/selfHighlight/enableTaskbarFlashing", true};
|
||||
QStringSetting selfHighlightSoundUrl = {
|
||||
"/highlighting/selfHighlightSoundUrl", ""};
|
||||
QStringSetting selfHighlightColor = {"/highlighting/selfHighlightColor",
|
||||
""};
|
||||
|
||||
BoolSetting enableWhisperHighlight = {
|
||||
"/highlighting/whisperHighlight/whispersHighlighted", true};
|
||||
BoolSetting enableWhisperHighlightSound = {
|
||||
"/highlighting/whisperHighlight/enableSound", false};
|
||||
BoolSetting enableWhisperHighlightTaskbar = {
|
||||
"/highlighting/whisperHighlight/enableTaskbarFlashing", false};
|
||||
QStringSetting whisperHighlightSoundUrl = {
|
||||
"/highlighting/whisperHighlightSoundUrl", ""};
|
||||
QStringSetting whisperHighlightColor = {
|
||||
"/highlighting/whisperHighlightColor", ""};
|
||||
|
||||
BoolSetting enableSubHighlight = {
|
||||
"/highlighting/subHighlight/subsHighlighted", true};
|
||||
BoolSetting enableSubHighlightSound = {
|
||||
"/highlighting/subHighlight/enableSound", false};
|
||||
BoolSetting enableSubHighlightTaskbar = {
|
||||
"/highlighting/subHighlight/enableTaskbarFlashing", false};
|
||||
QStringSetting subHighlightSoundUrl = {"/highlighting/subHighlightSoundUrl",
|
||||
""};
|
||||
QStringSetting subHighlightColor = {"/highlighting/subHighlightColor", ""};
|
||||
|
||||
QStringSetting highlightColor = {"/highlighting/color", ""};
|
||||
|
||||
BoolSetting longAlerts = {"/highlighting/alerts", false};
|
||||
|
@ -168,7 +189,7 @@ public:
|
|||
QStringSetting logPath = {"/logging/path", ""};
|
||||
|
||||
QStringSetting pathHighlightSound = {"/highlighting/highlightSoundPath",
|
||||
"qrc:/sounds/ping2.wav"};
|
||||
""};
|
||||
|
||||
BoolSetting highlightAlwaysPlaySound = {"/highlighting/alwaysPlaySound",
|
||||
false};
|
||||
|
|
|
@ -38,9 +38,6 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
|||
|
||||
this->splits.resizeHandle = QColor(0, 148, 255, 0xff);
|
||||
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x50);
|
||||
|
||||
// Highlighted Messages: theme support quick-fix
|
||||
this->messages.backgrounds.highlighted = QColor("#BD8489");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -49,11 +46,10 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
|||
|
||||
this->splits.resizeHandle = QColor(0, 148, 255, 0x70);
|
||||
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x20);
|
||||
|
||||
// Highlighted Messages: theme support quick-fix
|
||||
this->messages.backgrounds.highlighted = QColor("#4B282C");
|
||||
}
|
||||
|
||||
this->messages.backgrounds.highlighted = QColor(140, 84, 89, 127);
|
||||
|
||||
this->splits.header.background = getColor(0, sat, flat ? 1 : 0.9);
|
||||
this->splits.header.border = getColor(0, sat, flat ? 1 : 0.85);
|
||||
this->splits.header.text = this->messages.textColors.regular;
|
||||
|
|
|
@ -22,6 +22,19 @@ static void setStringItem(QStandardItem *item, const QString &value,
|
|||
(editable ? (Qt::ItemIsEditable) : 0)));
|
||||
}
|
||||
|
||||
static void setFilePathItem(QStandardItem *item, const QUrl &value)
|
||||
{
|
||||
item->setData(value, Qt::UserRole);
|
||||
item->setData(value.fileName(), Qt::DisplayRole);
|
||||
item->setFlags(Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable));
|
||||
}
|
||||
|
||||
static void setColorItem(QStandardItem *item, const QColor &value)
|
||||
{
|
||||
item->setData(value, Qt::DecorationRole);
|
||||
item->setFlags(Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable));
|
||||
}
|
||||
|
||||
static QStandardItem *emptyItem()
|
||||
{
|
||||
auto *item = new QStandardItem();
|
||||
|
|
|
@ -282,18 +282,7 @@ void Scrollbar::paintEvent(QPaintEvent *)
|
|||
|
||||
if (!highlight.isNull())
|
||||
{
|
||||
QColor color = [&] {
|
||||
switch (highlight.getColor())
|
||||
{
|
||||
case ScrollbarHighlight::Highlight:
|
||||
return getApp()
|
||||
->themes->scrollbars.highlights.highlight;
|
||||
case ScrollbarHighlight::Subscription:
|
||||
return getApp()
|
||||
->themes->scrollbars.highlights.subscription;
|
||||
}
|
||||
return QColor();
|
||||
}();
|
||||
QColor color = highlight.getColor();
|
||||
|
||||
switch (highlight.getStyle())
|
||||
{
|
||||
|
|
367
src/widgets/dialogs/ColorPickerDialog.cpp
Normal file
367
src/widgets/dialogs/ColorPickerDialog.cpp
Normal file
|
@ -0,0 +1,367 @@
|
|||
#include "widgets/dialogs/ColorPickerDialog.hpp"
|
||||
|
||||
#include "providers/colors/ColorProvider.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
ColorPickerDialog::ColorPickerDialog(const QColor &initial, QWidget *parent)
|
||||
: BasePopup(BaseWindow::EnableCustomFrame, parent)
|
||||
, color_()
|
||||
, dialogConfirmed_(false)
|
||||
{
|
||||
// This hosts the "business logic" and the dialog button box
|
||||
LayoutCreator<QWidget> layoutWidget(this->getLayoutContainer());
|
||||
auto layout = layoutWidget.setLayoutType<QVBoxLayout>().withoutMargin();
|
||||
|
||||
// This hosts the business logic: color picker and predefined colors
|
||||
LayoutCreator<QWidget> contentCreator(new QWidget());
|
||||
auto contents = contentCreator.setLayoutType<QHBoxLayout>();
|
||||
|
||||
// This hosts the predefined colors (and also the currently selected color)
|
||||
LayoutCreator<QWidget> predefCreator(new QWidget());
|
||||
auto predef = predefCreator.setLayoutType<QVBoxLayout>();
|
||||
|
||||
// Recently used colors
|
||||
{
|
||||
LayoutCreator<QWidget> gridCreator(new QWidget());
|
||||
this->initRecentColors(gridCreator);
|
||||
|
||||
predef.append(gridCreator.getElement());
|
||||
}
|
||||
|
||||
// Default colors
|
||||
{
|
||||
LayoutCreator<QWidget> gridCreator(new QWidget());
|
||||
this->initDefaultColors(gridCreator);
|
||||
|
||||
predef.append(gridCreator.getElement());
|
||||
}
|
||||
|
||||
// Currently selected color
|
||||
{
|
||||
LayoutCreator<QWidget> curColorCreator(new QWidget());
|
||||
auto curColor = curColorCreator.setLayoutType<QHBoxLayout>();
|
||||
curColor.emplace<QLabel>("Selected:").assign(&this->ui_.selected.label);
|
||||
curColor.emplace<ColorButton>(initial).assign(
|
||||
&this->ui_.selected.color);
|
||||
|
||||
predef.append(curColor.getElement());
|
||||
}
|
||||
|
||||
contents.append(predef.getElement());
|
||||
|
||||
// Color picker
|
||||
{
|
||||
LayoutCreator<QWidget> obj(new QWidget());
|
||||
auto vbox = obj.setLayoutType<QVBoxLayout>();
|
||||
|
||||
// The actual color picker
|
||||
{
|
||||
LayoutCreator<QWidget> cpCreator(new QWidget());
|
||||
this->initColorPicker(cpCreator);
|
||||
|
||||
vbox.append(cpCreator.getElement());
|
||||
}
|
||||
|
||||
// Spin boxes
|
||||
{
|
||||
LayoutCreator<QWidget> sbCreator(new QWidget());
|
||||
this->initSpinBoxes(sbCreator);
|
||||
|
||||
vbox.append(sbCreator.getElement());
|
||||
}
|
||||
|
||||
// HTML color
|
||||
{
|
||||
LayoutCreator<QWidget> htmlCreator(new QWidget());
|
||||
this->initHtmlColor(htmlCreator);
|
||||
|
||||
vbox.append(htmlCreator.getElement());
|
||||
}
|
||||
|
||||
contents.append(obj.getElement());
|
||||
}
|
||||
|
||||
layout.append(contents.getElement());
|
||||
|
||||
// Dialog buttons
|
||||
auto buttons =
|
||||
layout.emplace<QHBoxLayout>().emplace<QDialogButtonBox>(this);
|
||||
{
|
||||
auto *button_ok = buttons->addButton(QDialogButtonBox::Ok);
|
||||
QObject::connect(button_ok, &QPushButton::clicked,
|
||||
[=](bool) { this->ok(); });
|
||||
auto *button_cancel = buttons->addButton(QDialogButtonBox::Cancel);
|
||||
QObject::connect(button_cancel, &QAbstractButton::clicked,
|
||||
[=](bool) { this->close(); });
|
||||
}
|
||||
|
||||
this->themeChangedEvent();
|
||||
this->selectColor(initial, false);
|
||||
}
|
||||
|
||||
ColorPickerDialog::~ColorPickerDialog()
|
||||
{
|
||||
if (this->htmlColorValidator_)
|
||||
{
|
||||
this->htmlColorValidator_->deleteLater();
|
||||
this->htmlColorValidator_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
QColor ColorPickerDialog::selectedColor() const
|
||||
{
|
||||
if (!this->dialogConfirmed_)
|
||||
{
|
||||
// If the Cancel button was clicked, return the invalid color
|
||||
return QColor();
|
||||
}
|
||||
|
||||
return this->color_;
|
||||
}
|
||||
|
||||
void ColorPickerDialog::closeEvent(QCloseEvent *)
|
||||
{
|
||||
this->closed.invoke();
|
||||
}
|
||||
|
||||
void ColorPickerDialog::themeChangedEvent()
|
||||
{
|
||||
BaseWindow::themeChangedEvent();
|
||||
|
||||
QString textCol = this->theme->splits.input.text.name(QColor::HexRgb);
|
||||
QString bgCol = this->theme->splits.input.background.name(QColor::HexRgb);
|
||||
|
||||
// Labels
|
||||
|
||||
QString labelStyle = QString("color: %1;").arg(textCol);
|
||||
|
||||
this->ui_.recent.label->setStyleSheet(labelStyle);
|
||||
this->ui_.def.label->setStyleSheet(labelStyle);
|
||||
this->ui_.selected.label->setStyleSheet(labelStyle);
|
||||
this->ui_.picker.htmlLabel->setStyleSheet(labelStyle);
|
||||
|
||||
for (auto spinBoxLabel : this->ui_.picker.spinBoxLabels)
|
||||
{
|
||||
spinBoxLabel->setStyleSheet(labelStyle);
|
||||
}
|
||||
|
||||
this->ui_.picker.htmlEdit->setStyleSheet(
|
||||
this->theme->splits.input.styleSheet);
|
||||
|
||||
// Styling spin boxes is too much effort
|
||||
}
|
||||
|
||||
void ColorPickerDialog::selectColor(const QColor &color, bool fromColorPicker)
|
||||
{
|
||||
if (color == this->color_)
|
||||
return;
|
||||
|
||||
this->color_ = color;
|
||||
|
||||
// Update UI elements
|
||||
this->ui_.selected.color->setColor(this->color_);
|
||||
|
||||
/*
|
||||
* Somewhat "ugly" hack to prevent feedback loop between widgets. Since
|
||||
* this method is private, I'm okay with this being ugly.
|
||||
*/
|
||||
if (!fromColorPicker)
|
||||
{
|
||||
this->ui_.picker.colorPicker->setCol(this->color_.hslHue(),
|
||||
this->color_.hslSaturation());
|
||||
this->ui_.picker.luminancePicker->setCol(this->color_.hsvHue(),
|
||||
this->color_.hsvSaturation(),
|
||||
this->color_.value());
|
||||
}
|
||||
|
||||
this->ui_.picker.spinBoxes[SpinBox::RED]->setValue(this->color_.red());
|
||||
this->ui_.picker.spinBoxes[SpinBox::GREEN]->setValue(this->color_.green());
|
||||
this->ui_.picker.spinBoxes[SpinBox::BLUE]->setValue(this->color_.blue());
|
||||
this->ui_.picker.spinBoxes[SpinBox::ALPHA]->setValue(this->color_.alpha());
|
||||
|
||||
/*
|
||||
* Here, we are intentionally using HexRgb instead of HexArgb. Most online
|
||||
* sites (or other applications) will likely not include the alpha channel
|
||||
* in their output.
|
||||
*/
|
||||
this->ui_.picker.htmlEdit->setText(this->color_.name(QColor::HexRgb));
|
||||
}
|
||||
|
||||
void ColorPickerDialog::ok()
|
||||
{
|
||||
this->dialogConfirmed_ = true;
|
||||
this->close();
|
||||
}
|
||||
|
||||
void ColorPickerDialog::initRecentColors(LayoutCreator<QWidget> &creator)
|
||||
{
|
||||
auto grid = creator.setLayoutType<QGridLayout>();
|
||||
|
||||
auto label = this->ui_.recent.label = new QLabel("Recently used:");
|
||||
grid->addWidget(label, 0, 0, 1, -1);
|
||||
|
||||
const auto recentColors = ColorProvider::instance().recentColors();
|
||||
auto it = recentColors.begin();
|
||||
size_t ind = 0;
|
||||
while (it != recentColors.end() && ind < MAX_RECENT_COLORS)
|
||||
{
|
||||
this->ui_.recent.colors.push_back(new ColorButton(*it, this));
|
||||
auto *button = this->ui_.recent.colors[ind];
|
||||
|
||||
const int rowInd = (ind / RECENT_COLORS_PER_ROW) + 1;
|
||||
const int columnInd = ind % RECENT_COLORS_PER_ROW;
|
||||
|
||||
grid->addWidget(button, rowInd, columnInd);
|
||||
|
||||
QObject::connect(button, &QPushButton::clicked,
|
||||
[=] { this->selectColor(button->color(), false); });
|
||||
|
||||
++it;
|
||||
++ind;
|
||||
}
|
||||
|
||||
auto spacer =
|
||||
new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
|
||||
grid->addItem(spacer, (ind / RECENT_COLORS_PER_ROW) + 2, 0, 1, 1,
|
||||
Qt::AlignTop);
|
||||
}
|
||||
|
||||
void ColorPickerDialog::initDefaultColors(LayoutCreator<QWidget> &creator)
|
||||
{
|
||||
auto grid = creator.setLayoutType<QGridLayout>();
|
||||
|
||||
auto label = this->ui_.def.label = new QLabel("Default colors:");
|
||||
grid->addWidget(label, 0, 0, 1, -1);
|
||||
|
||||
const auto defaultColors = ColorProvider::instance().defaultColors();
|
||||
auto it = defaultColors.begin();
|
||||
size_t ind = 0;
|
||||
while (it != defaultColors.end())
|
||||
{
|
||||
this->ui_.def.colors.push_back(new ColorButton(*it, this));
|
||||
auto *button = this->ui_.def.colors[ind];
|
||||
|
||||
const int rowInd = (ind / DEFAULT_COLORS_PER_ROW) + 1;
|
||||
const int columnInd = ind % DEFAULT_COLORS_PER_ROW;
|
||||
|
||||
grid->addWidget(button, rowInd, columnInd);
|
||||
|
||||
QObject::connect(button, &QPushButton::clicked,
|
||||
[=] { this->selectColor(button->color(), false); });
|
||||
|
||||
++it;
|
||||
++ind;
|
||||
}
|
||||
|
||||
auto spacer =
|
||||
new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
|
||||
grid->addItem(spacer, (ind / DEFAULT_COLORS_PER_ROW) + 2, 0, 1, 1,
|
||||
Qt::AlignTop);
|
||||
}
|
||||
|
||||
void ColorPickerDialog::initColorPicker(LayoutCreator<QWidget> &creator)
|
||||
{
|
||||
auto cpPanel = creator.setLayoutType<QHBoxLayout>();
|
||||
|
||||
/*
|
||||
* For some reason, LayoutCreator::emplace didn't work for these.
|
||||
* (Or maybe I was too dense to make it work.)
|
||||
* After trying to debug for 4 hours or so, I gave up and settled
|
||||
* for this solution.
|
||||
*/
|
||||
auto *colorPicker = new QColorPicker(this);
|
||||
this->ui_.picker.colorPicker = colorPicker;
|
||||
|
||||
auto *luminancePicker = new QColorLuminancePicker(this);
|
||||
this->ui_.picker.luminancePicker = luminancePicker;
|
||||
|
||||
cpPanel.append(colorPicker);
|
||||
cpPanel.append(luminancePicker);
|
||||
|
||||
QObject::connect(colorPicker, SIGNAL(newCol(int, int)), luminancePicker,
|
||||
SLOT(setCol(int, int)));
|
||||
|
||||
QObject::connect(
|
||||
luminancePicker, &QColorLuminancePicker::newHsv,
|
||||
[=](int h, int s, int v) {
|
||||
int alpha = this->ui_.picker.spinBoxes[SpinBox::ALPHA]->value();
|
||||
this->selectColor(QColor::fromHsv(h, s, v, alpha), true);
|
||||
});
|
||||
}
|
||||
|
||||
void ColorPickerDialog::initSpinBoxes(LayoutCreator<QWidget> &creator)
|
||||
{
|
||||
auto spinBoxes = creator.setLayoutType<QGridLayout>();
|
||||
|
||||
auto *red = this->ui_.picker.spinBoxes[SpinBox::RED] =
|
||||
new QColSpinBox(this);
|
||||
auto *green = this->ui_.picker.spinBoxes[SpinBox::GREEN] =
|
||||
new QColSpinBox(this);
|
||||
auto *blue = this->ui_.picker.spinBoxes[SpinBox::BLUE] =
|
||||
new QColSpinBox(this);
|
||||
auto *alpha = this->ui_.picker.spinBoxes[SpinBox::ALPHA] =
|
||||
new QColSpinBox(this);
|
||||
|
||||
// We need pointers to these for theme changes
|
||||
auto *redLbl = this->ui_.picker.spinBoxLabels[SpinBox::RED] =
|
||||
new QLabel("Red:");
|
||||
auto *greenLbl = this->ui_.picker.spinBoxLabels[SpinBox::GREEN] =
|
||||
new QLabel("Green:");
|
||||
auto *blueLbl = this->ui_.picker.spinBoxLabels[SpinBox::BLUE] =
|
||||
new QLabel("Blue:");
|
||||
auto *alphaLbl = this->ui_.picker.spinBoxLabels[SpinBox::ALPHA] =
|
||||
new QLabel("Alpha:");
|
||||
|
||||
spinBoxes->addWidget(redLbl, 0, 0);
|
||||
spinBoxes->addWidget(red, 0, 1);
|
||||
|
||||
spinBoxes->addWidget(greenLbl, 1, 0);
|
||||
spinBoxes->addWidget(green, 1, 1);
|
||||
|
||||
spinBoxes->addWidget(blueLbl, 2, 0);
|
||||
spinBoxes->addWidget(blue, 2, 1);
|
||||
|
||||
spinBoxes->addWidget(alphaLbl, 3, 0);
|
||||
spinBoxes->addWidget(alpha, 3, 1);
|
||||
|
||||
for (size_t i = 0; i < SpinBox::END; ++i)
|
||||
{
|
||||
QObject::connect(
|
||||
this->ui_.picker.spinBoxes[i],
|
||||
QOverload<int>::of(&QSpinBox::valueChanged), [=](int value) {
|
||||
this->selectColor(QColor(red->value(), green->value(),
|
||||
blue->value(), alpha->value()),
|
||||
false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ColorPickerDialog::initHtmlColor(LayoutCreator<QWidget> &creator)
|
||||
{
|
||||
auto html = creator.setLayoutType<QGridLayout>();
|
||||
|
||||
// Copied from Qt source for QColorShower
|
||||
static QRegularExpression regExp(
|
||||
QStringLiteral("#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})"));
|
||||
auto *validator = this->htmlColorValidator_ =
|
||||
new QRegularExpressionValidator(regExp, this);
|
||||
|
||||
auto *htmlLabel = this->ui_.picker.htmlLabel = new QLabel("HTML:");
|
||||
auto *htmlEdit = this->ui_.picker.htmlEdit = new QLineEdit(this);
|
||||
|
||||
htmlEdit->setValidator(validator);
|
||||
|
||||
html->addWidget(htmlLabel, 0, 0);
|
||||
html->addWidget(htmlEdit, 0, 1);
|
||||
|
||||
QObject::connect(htmlEdit, &QLineEdit::textEdited,
|
||||
[=](const QString &text) {
|
||||
QColor col(text);
|
||||
if (col.isValid())
|
||||
this->selectColor(col, false);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
108
src/widgets/dialogs/ColorPickerDialog.hpp
Normal file
108
src/widgets/dialogs/ColorPickerDialog.hpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
#pragma once
|
||||
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/BasePopup.hpp"
|
||||
#include "widgets/helper/ColorButton.hpp"
|
||||
#include "widgets/helper/QColorPicker.hpp"
|
||||
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
/**
|
||||
* @brief A custom color picker dialog.
|
||||
*
|
||||
* This class exists because QColorPickerDialog did not suit our use case.
|
||||
* This dialog provides buttons for recently used and default colors, as well
|
||||
* as a color picker widget identical to the one used in QColorPickerDialog.
|
||||
*/
|
||||
class ColorPickerDialog : public BasePopup
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Create a new color picker dialog that selects the initial color.
|
||||
*
|
||||
* You can connect to the ::closed signal of this instance to get notified
|
||||
* when the dialog is closed.
|
||||
*/
|
||||
ColorPickerDialog(const QColor &initial, QWidget *parent = nullptr);
|
||||
|
||||
~ColorPickerDialog();
|
||||
|
||||
/**
|
||||
* @brief Return the final color selected by the user.
|
||||
*
|
||||
* Note that this method will always return the invalid color if the dialog
|
||||
* is still open, or if the dialog has not been confirmed.
|
||||
*
|
||||
* @return The color selected by the user, if the dialog was confirmed.
|
||||
* The invalid color, if the dialog has not been confirmed.
|
||||
*/
|
||||
QColor selectedColor() const;
|
||||
|
||||
pajlada::Signals::NoArgSignal closed;
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *);
|
||||
void themeChangedEvent();
|
||||
|
||||
private:
|
||||
struct {
|
||||
struct {
|
||||
QLabel *label;
|
||||
std::vector<ColorButton *> colors;
|
||||
} recent;
|
||||
|
||||
struct {
|
||||
QLabel *label;
|
||||
std::vector<ColorButton *> colors;
|
||||
} def;
|
||||
|
||||
struct {
|
||||
QLabel *label;
|
||||
ColorButton *color;
|
||||
} selected;
|
||||
|
||||
struct {
|
||||
QColorPicker *colorPicker;
|
||||
QColorLuminancePicker *luminancePicker;
|
||||
|
||||
std::array<QLabel *, 4> spinBoxLabels;
|
||||
std::array<QColSpinBox *, 4> spinBoxes;
|
||||
|
||||
QLabel *htmlLabel;
|
||||
QLineEdit *htmlEdit;
|
||||
} picker;
|
||||
} ui_;
|
||||
|
||||
enum SpinBox : size_t { RED = 0, GREEN = 1, BLUE = 2, ALPHA = 3, END };
|
||||
|
||||
static const size_t MAX_RECENT_COLORS = 10;
|
||||
static const size_t RECENT_COLORS_PER_ROW = 5;
|
||||
static const size_t DEFAULT_COLORS_PER_ROW = 5;
|
||||
|
||||
QColor color_;
|
||||
bool dialogConfirmed_;
|
||||
QRegularExpressionValidator *htmlColorValidator_{};
|
||||
|
||||
/**
|
||||
* @brief Update the currently selected color.
|
||||
*
|
||||
* @param color Color to update to.
|
||||
* @param fromColorPicker Whether the color update has been triggered by
|
||||
* one of the color picker widgets. This is needed
|
||||
* to prevent weird widget behavior.
|
||||
*/
|
||||
void selectColor(const QColor &color, bool fromColorPicker);
|
||||
|
||||
/// Called when the dialog is confirmed.
|
||||
void ok();
|
||||
|
||||
// Helper methods for initializing UI elements
|
||||
void initRecentColors(LayoutCreator<QWidget> &creator);
|
||||
void initDefaultColors(LayoutCreator<QWidget> &creator);
|
||||
void initColorPicker(LayoutCreator<QWidget> &creator);
|
||||
void initSpinBoxes(LayoutCreator<QWidget> &creator);
|
||||
void initHtmlColor(LayoutCreator<QWidget> &creator);
|
||||
};
|
||||
} // namespace chatterino
|
23
src/widgets/helper/ColorButton.cpp
Normal file
23
src/widgets/helper/ColorButton.cpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#include "widgets/helper/ColorButton.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
ColorButton::ColorButton(const QColor &color, QWidget *parent)
|
||||
: QPushButton(parent)
|
||||
, color_(color)
|
||||
{
|
||||
this->setColor(color_);
|
||||
}
|
||||
|
||||
const QColor &ColorButton::color() const
|
||||
{
|
||||
return this->color_;
|
||||
}
|
||||
|
||||
void ColorButton::setColor(QColor color)
|
||||
{
|
||||
this->color_ = color;
|
||||
this->setStyleSheet("background-color: " + color.name(QColor::HexArgb));
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
18
src/widgets/helper/ColorButton.hpp
Normal file
18
src/widgets/helper/ColorButton.hpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class ColorButton : public QPushButton
|
||||
{
|
||||
public:
|
||||
ColorButton(const QColor &color, QWidget *parent = nullptr);
|
||||
|
||||
const QColor &color() const;
|
||||
|
||||
void setColor(QColor color);
|
||||
|
||||
private:
|
||||
QColor color_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
290
src/widgets/helper/QColorPicker.cpp
Normal file
290
src/widgets/helper/QColorPicker.cpp
Normal file
|
@ -0,0 +1,290 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtWidgets module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
#include "widgets/helper/QColorPicker.hpp"
|
||||
|
||||
#include <qdrawutil.h>
|
||||
|
||||
/*
|
||||
* These classes are literally copied from the Qt source.
|
||||
* Unfortunately, they are private to the QColorDialog class so we cannot use
|
||||
* them directly.
|
||||
* If they become public at any point in the future, it should be possible to
|
||||
* replace every include of this header with the respective includes for the
|
||||
* QColorPicker, QColorLuminancePicker, and QColSpinBox classes.
|
||||
*/
|
||||
namespace chatterino {
|
||||
|
||||
int QColorLuminancePicker::y2val(int y)
|
||||
{
|
||||
int d = height() - 2 * coff - 1;
|
||||
return 255 - (y - coff) * 255 / d;
|
||||
}
|
||||
|
||||
int QColorLuminancePicker::val2y(int v)
|
||||
{
|
||||
int d = height() - 2 * coff - 1;
|
||||
return coff + (255 - v) * d / 255;
|
||||
}
|
||||
|
||||
QColorLuminancePicker::QColorLuminancePicker(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
hue = 100;
|
||||
val = 100;
|
||||
sat = 100;
|
||||
pix = 0;
|
||||
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
|
||||
}
|
||||
|
||||
QColorLuminancePicker::~QColorLuminancePicker()
|
||||
{
|
||||
delete pix;
|
||||
}
|
||||
|
||||
void QColorLuminancePicker::mouseMoveEvent(QMouseEvent *m)
|
||||
{
|
||||
setVal(y2val(m->y()));
|
||||
}
|
||||
|
||||
void QColorLuminancePicker::mousePressEvent(QMouseEvent *m)
|
||||
{
|
||||
setVal(y2val(m->y()));
|
||||
}
|
||||
|
||||
void QColorLuminancePicker::setVal(int v)
|
||||
{
|
||||
if (val == v)
|
||||
return;
|
||||
val = qMax(0, qMin(v, 255));
|
||||
delete pix;
|
||||
pix = 0;
|
||||
repaint();
|
||||
emit newHsv(hue, sat, val);
|
||||
}
|
||||
|
||||
//receives from a hue,sat chooser and relays.
|
||||
void QColorLuminancePicker::setCol(int h, int s)
|
||||
{
|
||||
setCol(h, s, val);
|
||||
emit newHsv(h, s, val);
|
||||
}
|
||||
|
||||
QSize QColorLuminancePicker::sizeHint() const
|
||||
{
|
||||
return QSize(LUMINANCE_PICKER_WIDTH, LUMINANCE_PICKER_HEIGHT);
|
||||
}
|
||||
|
||||
void QColorLuminancePicker::paintEvent(QPaintEvent *)
|
||||
{
|
||||
int w = width() - 5;
|
||||
QRect r(0, foff, w, height() - 2 * foff);
|
||||
int wi = r.width() - 2;
|
||||
int hi = r.height() - 2;
|
||||
if (!pix || pix->height() != hi || pix->width() != wi)
|
||||
{
|
||||
delete pix;
|
||||
QImage img(wi, hi, QImage::Format_RGB32);
|
||||
int y;
|
||||
uint *pixel = (uint *)img.scanLine(0);
|
||||
for (y = 0; y < hi; y++)
|
||||
{
|
||||
uint *end = pixel + wi;
|
||||
std::fill(pixel, end,
|
||||
QColor::fromHsv(hue, sat, y2val(y + coff)).rgb());
|
||||
pixel = end;
|
||||
}
|
||||
pix = new QPixmap(QPixmap::fromImage(img));
|
||||
}
|
||||
QPainter p(this);
|
||||
p.drawPixmap(1, coff, *pix);
|
||||
const QPalette &g = palette();
|
||||
qDrawShadePanel(&p, r, g, true);
|
||||
p.setPen(g.windowText().color());
|
||||
p.setBrush(g.windowText());
|
||||
QPolygon a;
|
||||
int y = val2y(val);
|
||||
a.setPoints(3, w, y, w + 5, y + 5, w + 5, y - 5);
|
||||
p.eraseRect(w, 0, 5, height());
|
||||
p.drawPolygon(a);
|
||||
}
|
||||
|
||||
void QColorLuminancePicker::setCol(int h, int s, int v)
|
||||
{
|
||||
val = v;
|
||||
hue = h;
|
||||
sat = s;
|
||||
delete pix;
|
||||
pix = 0;
|
||||
repaint();
|
||||
}
|
||||
|
||||
QPoint QColorPicker::colPt()
|
||||
{
|
||||
QRect r = contentsRect();
|
||||
return QPoint((360 - hue) * (r.width() - 1) / 360,
|
||||
(255 - sat) * (r.height() - 1) / 255);
|
||||
}
|
||||
|
||||
int QColorPicker::huePt(const QPoint &pt)
|
||||
{
|
||||
QRect r = contentsRect();
|
||||
return 360 - pt.x() * 360 / (r.width() - 1);
|
||||
}
|
||||
|
||||
int QColorPicker::satPt(const QPoint &pt)
|
||||
{
|
||||
QRect r = contentsRect();
|
||||
return 255 - pt.y() * 255 / (r.height() - 1);
|
||||
}
|
||||
|
||||
void QColorPicker::setCol(const QPoint &pt)
|
||||
{
|
||||
setCol(huePt(pt), satPt(pt));
|
||||
}
|
||||
|
||||
QColorPicker::QColorPicker(QWidget *parent)
|
||||
: QFrame(parent)
|
||||
, crossVisible(true)
|
||||
{
|
||||
hue = 0;
|
||||
sat = 0;
|
||||
setCol(150, 255);
|
||||
setAttribute(Qt::WA_NoSystemBackground);
|
||||
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
|
||||
}
|
||||
|
||||
QColorPicker::~QColorPicker()
|
||||
{
|
||||
}
|
||||
|
||||
void QColorPicker::setCrossVisible(bool visible)
|
||||
{
|
||||
if (crossVisible != visible)
|
||||
{
|
||||
crossVisible = visible;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
QSize QColorPicker::sizeHint() const
|
||||
{
|
||||
return QSize(COLOR_PICKER_WIDTH, COLOR_PICKER_HEIGHT);
|
||||
}
|
||||
|
||||
void QColorPicker::setCol(int h, int s)
|
||||
{
|
||||
int nhue = qMin(qMax(0, h), 359);
|
||||
int nsat = qMin(qMax(0, s), 255);
|
||||
if (nhue == hue && nsat == sat)
|
||||
return;
|
||||
QRect r(colPt(), QSize(20, 20));
|
||||
hue = nhue;
|
||||
sat = nsat;
|
||||
r = r.united(QRect(colPt(), QSize(20, 20)));
|
||||
r.translate(contentsRect().x() - 9, contentsRect().y() - 9);
|
||||
repaint(r);
|
||||
}
|
||||
|
||||
void QColorPicker::mouseMoveEvent(QMouseEvent *m)
|
||||
{
|
||||
QPoint p = m->pos() - contentsRect().topLeft();
|
||||
setCol(p);
|
||||
emit newCol(hue, sat);
|
||||
}
|
||||
|
||||
void QColorPicker::mousePressEvent(QMouseEvent *m)
|
||||
{
|
||||
QPoint p = m->pos() - contentsRect().topLeft();
|
||||
setCol(p);
|
||||
emit newCol(hue, sat);
|
||||
}
|
||||
|
||||
void QColorPicker::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter p(this);
|
||||
drawFrame(&p);
|
||||
QRect r = contentsRect();
|
||||
p.drawPixmap(r.topLeft(), pix);
|
||||
if (crossVisible)
|
||||
{
|
||||
QPoint pt = colPt() + r.topLeft();
|
||||
p.setPen(Qt::black);
|
||||
p.fillRect(pt.x() - 9, pt.y(), 20, 2, Qt::black);
|
||||
p.fillRect(pt.x(), pt.y() - 9, 2, 20, Qt::black);
|
||||
}
|
||||
}
|
||||
|
||||
void QColorPicker::resizeEvent(QResizeEvent *ev)
|
||||
{
|
||||
QFrame::resizeEvent(ev);
|
||||
int w = width() - frameWidth() * 2;
|
||||
int h = height() - frameWidth() * 2;
|
||||
QImage img(w, h, QImage::Format_RGB32);
|
||||
int x, y;
|
||||
uint *pixel = (uint *)img.scanLine(0);
|
||||
for (y = 0; y < h; y++)
|
||||
{
|
||||
const uint *end = pixel + w;
|
||||
x = 0;
|
||||
while (pixel < end)
|
||||
{
|
||||
QPoint p(x, y);
|
||||
QColor c;
|
||||
c.setHsv(huePt(p), satPt(p), 200);
|
||||
*pixel = c.rgb();
|
||||
++pixel;
|
||||
++x;
|
||||
}
|
||||
}
|
||||
pix = QPixmap::fromImage(img);
|
||||
}
|
||||
|
||||
QColSpinBox::QColSpinBox(QWidget *parent)
|
||||
: QSpinBox(parent)
|
||||
{
|
||||
this->setRange(0, 255);
|
||||
}
|
||||
|
||||
void QColSpinBox::setValue(int i)
|
||||
{
|
||||
const QSignalBlocker blocker(this);
|
||||
QSpinBox::setValue(i);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
130
src/widgets/helper/QColorPicker.hpp
Normal file
130
src/widgets/helper/QColorPicker.hpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtWidgets module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <QSpinBox>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
/*
|
||||
* These classes are literally copied from the Qt source.
|
||||
* Unfortunately, they are private to the QColorDialog class so we cannot use
|
||||
* them directly.
|
||||
* If they become public at any point in the future, it should be possible to
|
||||
* replace every include of this header with the respective includes for the
|
||||
* QColorPicker, QColorLuminancePicker, and QColSpinBox classes.
|
||||
*/
|
||||
class QColorPicker : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QColorPicker(QWidget *parent);
|
||||
~QColorPicker();
|
||||
void setCrossVisible(bool visible);
|
||||
|
||||
public slots:
|
||||
void setCol(int h, int s);
|
||||
|
||||
signals:
|
||||
void newCol(int h, int s);
|
||||
|
||||
protected:
|
||||
QSize sizeHint() const override;
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
void mouseMoveEvent(QMouseEvent *) override;
|
||||
void mousePressEvent(QMouseEvent *) override;
|
||||
void resizeEvent(QResizeEvent *) override;
|
||||
|
||||
private:
|
||||
int hue;
|
||||
int sat;
|
||||
QPoint colPt();
|
||||
int huePt(const QPoint &pt);
|
||||
int satPt(const QPoint &pt);
|
||||
void setCol(const QPoint &pt);
|
||||
QPixmap pix;
|
||||
bool crossVisible;
|
||||
};
|
||||
|
||||
static const int COLOR_PICKER_WIDTH = 220;
|
||||
static const int COLOR_PICKER_HEIGHT = 200;
|
||||
|
||||
class QColorLuminancePicker : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QColorLuminancePicker(QWidget *parent = 0);
|
||||
~QColorLuminancePicker();
|
||||
|
||||
public slots:
|
||||
void setCol(int h, int s, int v);
|
||||
void setCol(int h, int s);
|
||||
|
||||
signals:
|
||||
void newHsv(int h, int s, int v);
|
||||
|
||||
protected:
|
||||
QSize sizeHint() const override;
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
void mouseMoveEvent(QMouseEvent *) override;
|
||||
void mousePressEvent(QMouseEvent *) override;
|
||||
|
||||
private:
|
||||
enum { foff = 3, coff = 4 }; //frame and contents offset
|
||||
int val;
|
||||
int hue;
|
||||
int sat;
|
||||
int y2val(int y);
|
||||
int val2y(int val);
|
||||
void setVal(int v);
|
||||
QPixmap *pix;
|
||||
};
|
||||
|
||||
static const int LUMINANCE_PICKER_WIDTH = 25;
|
||||
static const int LUMINANCE_PICKER_HEIGHT = COLOR_PICKER_HEIGHT;
|
||||
|
||||
class QColSpinBox : public QSpinBox
|
||||
{
|
||||
public:
|
||||
QColSpinBox(QWidget *parent);
|
||||
|
||||
void setValue(int i);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -1,24 +1,27 @@
|
|||
#include "widgets/helper/ScrollbarHighlight.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "widgets/Scrollbar.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
ScrollbarHighlight::ScrollbarHighlight()
|
||||
: color_(Color::Highlight)
|
||||
: color_(std::make_shared<QColor>())
|
||||
, style_(Style::None)
|
||||
{
|
||||
}
|
||||
|
||||
ScrollbarHighlight::ScrollbarHighlight(Color color, Style style)
|
||||
ScrollbarHighlight::ScrollbarHighlight(const std::shared_ptr<QColor> color,
|
||||
Style style)
|
||||
: color_(color)
|
||||
, style_(style)
|
||||
{
|
||||
}
|
||||
|
||||
ScrollbarHighlight::Color ScrollbarHighlight::getColor() const
|
||||
QColor ScrollbarHighlight::getColor() const
|
||||
{
|
||||
return this->color_;
|
||||
return *this->color_;
|
||||
}
|
||||
|
||||
ScrollbarHighlight::Style ScrollbarHighlight::getStyle() const
|
||||
|
|
|
@ -6,17 +6,25 @@ class ScrollbarHighlight
|
|||
{
|
||||
public:
|
||||
enum Style : char { None, Default, Line };
|
||||
enum Color : char { Highlight, Subscription };
|
||||
|
||||
/**
|
||||
* @brief Constructs an invalid ScrollbarHighlight.
|
||||
*
|
||||
* A highlight constructed this way will not show on the scrollbar.
|
||||
* For these, use the static ScrollbarHighlight::newSubscription and
|
||||
* ScrollbarHighlight::newHighlight methods.
|
||||
*/
|
||||
ScrollbarHighlight();
|
||||
ScrollbarHighlight(Color color, Style style = Default);
|
||||
|
||||
Color getColor() const;
|
||||
ScrollbarHighlight(const std::shared_ptr<QColor> color,
|
||||
Style style = Default);
|
||||
|
||||
QColor getColor() const;
|
||||
Style getStyle() const;
|
||||
bool isNull() const;
|
||||
|
||||
private:
|
||||
Color color_;
|
||||
std::shared_ptr<QColor> color_;
|
||||
Style style_;
|
||||
};
|
||||
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
#include "controllers/highlights/HighlightModel.hpp"
|
||||
#include "controllers/highlights/UserHighlightModel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
#include "widgets/helper/EditableModelView.hpp"
|
||||
#include "widgets/dialogs/ColorPickerDialog.hpp"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QHeaderView>
|
||||
|
@ -45,8 +46,10 @@ HighlightingPage::HighlightingPage()
|
|||
// HIGHLIGHTS
|
||||
auto highlights = tabs.appendTab(new QVBoxLayout, "Messages");
|
||||
{
|
||||
highlights.emplace<QLabel>("Messages can be highlighted if "
|
||||
"they match a certain pattern.");
|
||||
highlights.emplace<QLabel>(
|
||||
"Messages can be highlighted if they match a certain "
|
||||
"pattern.\n"
|
||||
"NOTE: User highlights will override phrase highlights.");
|
||||
|
||||
EditableModelView *view =
|
||||
highlights
|
||||
|
@ -56,7 +59,8 @@ HighlightingPage::HighlightingPage()
|
|||
|
||||
view->addRegexHelpLink();
|
||||
view->setTitles({"Pattern", "Flash\ntaskbar", "Play\nsound",
|
||||
"Enable\nregex", "Case-\nsensitive"});
|
||||
"Enable\nregex", "Case-\nsensitive",
|
||||
"Custom\nsound", "Color"});
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
QHeaderView::Fixed);
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
|
@ -71,14 +75,22 @@ HighlightingPage::HighlightingPage()
|
|||
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()->highlights->phrases.appendItem(HighlightPhrase{
|
||||
"my phrase", true, false, false, false});
|
||||
"my phrase", true, false, false, false, "",
|
||||
*ColorProvider::instance().color(
|
||||
ColorType::SelfHighlight)});
|
||||
});
|
||||
|
||||
QObject::connect(view->getTableView(), &QTableView::clicked,
|
||||
[this, view](const QModelIndex &clicked) {
|
||||
this->tableCellClicked(clicked, view);
|
||||
});
|
||||
}
|
||||
|
||||
auto pingUsers = tabs.appendTab(new QVBoxLayout, "Users");
|
||||
{
|
||||
pingUsers.emplace<QLabel>(
|
||||
"Messages from a certain user can be highlighted.");
|
||||
"Messages from a certain user can be highlighted.\n"
|
||||
"NOTE: User highlights will override phrase highlights.");
|
||||
EditableModelView *view =
|
||||
pingUsers
|
||||
.emplace<EditableModelView>(
|
||||
|
@ -89,9 +101,10 @@ HighlightingPage::HighlightingPage()
|
|||
view->getTableView()->horizontalHeader()->hideSection(4);
|
||||
|
||||
// Case-sensitivity doesn't make sense for user names so it is
|
||||
// set to "false" by default & no checkbox is shown
|
||||
// set to "false" by default & the column is hidden
|
||||
view->setTitles({"Username", "Flash\ntaskbar", "Play\nsound",
|
||||
"Enable\nregex"});
|
||||
"Enable\nregex", "Case-\nsensitive",
|
||||
"Custom\nsound", "Color"});
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
QHeaderView::Fixed);
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
|
@ -107,8 +120,15 @@ HighlightingPage::HighlightingPage()
|
|||
view->addButtonPressed.connect([] {
|
||||
getApp()->highlights->highlightedUsers.appendItem(
|
||||
HighlightPhrase{"highlighted user", true, false, false,
|
||||
false});
|
||||
false, "",
|
||||
ColorProvider::instance().color(
|
||||
ColorType::SelfHighlight)});
|
||||
});
|
||||
|
||||
QObject::connect(view->getTableView(), &QTableView::clicked,
|
||||
[this, view](const QModelIndex &clicked) {
|
||||
this->tableCellClicked(clicked, view);
|
||||
});
|
||||
}
|
||||
|
||||
auto disabledUsers =
|
||||
|
@ -147,17 +167,33 @@ HighlightingPage::HighlightingPage()
|
|||
// MISC
|
||||
auto customSound = layout.emplace<QHBoxLayout>().withoutMargin();
|
||||
{
|
||||
customSound.append(this->createCheckBox(
|
||||
"Custom sound", getSettings()->customHighlightSound));
|
||||
auto fallbackSound = customSound.append(this->createCheckBox(
|
||||
"Fallback sound (played when no other sound is set)",
|
||||
getSettings()->customHighlightSound));
|
||||
|
||||
auto getSelectFileText = [] {
|
||||
const QString value = getSettings()->pathHighlightSound;
|
||||
return value.isEmpty() ? "Select custom fallback sound"
|
||||
: QUrl::fromLocalFile(value).fileName();
|
||||
};
|
||||
|
||||
auto selectFile =
|
||||
customSound.emplace<QPushButton>("Select custom sound file");
|
||||
QObject::connect(selectFile.getElement(), &QPushButton::clicked,
|
||||
this, [this] {
|
||||
auto fileName = QFileDialog::getOpenFileName(
|
||||
this, tr("Open Sound"), "",
|
||||
tr("Audio Files (*.mp3 *.wav)"));
|
||||
getSettings()->pathHighlightSound = fileName;
|
||||
});
|
||||
customSound.emplace<QPushButton>(getSelectFileText());
|
||||
|
||||
QObject::connect(
|
||||
selectFile.getElement(), &QPushButton::clicked, this,
|
||||
[=]() mutable {
|
||||
auto fileName = QFileDialog::getOpenFileName(
|
||||
this, tr("Open Sound"), "",
|
||||
tr("Audio Files (*.mp3 *.wav)"));
|
||||
|
||||
getSettings()->pathHighlightSound = fileName;
|
||||
selectFile.getElement()->setText(getSelectFileText());
|
||||
|
||||
// Set check box according to updated value
|
||||
fallbackSound->setCheckState(
|
||||
fileName.isEmpty() ? Qt::Unchecked : Qt::Checked);
|
||||
});
|
||||
}
|
||||
|
||||
layout.append(createCheckBox(ALWAYS_PLAY,
|
||||
|
@ -171,4 +207,60 @@ HighlightingPage::HighlightingPage()
|
|||
this->disabledUsersChangedTimer_.setSingleShot(true);
|
||||
}
|
||||
|
||||
void HighlightingPage::tableCellClicked(const QModelIndex &clicked,
|
||||
EditableModelView *view)
|
||||
{
|
||||
using Column = HighlightModel::Column;
|
||||
|
||||
if (clicked.column() == Column::SoundPath)
|
||||
{
|
||||
auto fileUrl = QFileDialog::getOpenFileUrl(
|
||||
this, tr("Open Sound"), QUrl(), tr("Audio Files (*.mp3 *.wav)"));
|
||||
view->getModel()->setData(clicked, fileUrl, Qt::UserRole);
|
||||
view->getModel()->setData(clicked, fileUrl.fileName(), Qt::DisplayRole);
|
||||
|
||||
// Enable custom sound check box if user set a sound
|
||||
if (!fileUrl.isEmpty())
|
||||
{
|
||||
QModelIndex checkBox = clicked.siblingAtColumn(Column::PlaySound);
|
||||
view->getModel()->setData(checkBox, Qt::Checked,
|
||||
Qt::CheckStateRole);
|
||||
}
|
||||
}
|
||||
else if (clicked.column() == Column::Color)
|
||||
{
|
||||
auto initial =
|
||||
view->getModel()->data(clicked, Qt::DecorationRole).value<QColor>();
|
||||
|
||||
auto dialog = new ColorPickerDialog(initial, this);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dialog->show();
|
||||
dialog->closed.connect([=] {
|
||||
QColor selected = dialog->selectedColor();
|
||||
|
||||
if (selected.isValid())
|
||||
{
|
||||
view->getModel()->setData(clicked, selected,
|
||||
Qt::DecorationRole);
|
||||
|
||||
// For special highlights we need to manually update the color map
|
||||
auto instance = ColorProvider::instance();
|
||||
switch (clicked.row())
|
||||
{
|
||||
case 0:
|
||||
instance.updateColor(ColorType::SelfHighlight,
|
||||
selected);
|
||||
break;
|
||||
case 1:
|
||||
instance.updateColor(ColorType::Whisper, selected);
|
||||
break;
|
||||
case 2:
|
||||
instance.updateColor(ColorType::Subscription, selected);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "widgets/helper/EditableModelView.hpp"
|
||||
#include "widgets/settingspages/SettingsPage.hpp"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
@ -17,6 +18,8 @@ public:
|
|||
|
||||
private:
|
||||
QTimer disabledUsersChangedTimer_;
|
||||
|
||||
void tableCellClicked(const QModelIndex &clicked, EditableModelView *view);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
Loading…
Reference in a new issue