added moderation buttons

This commit is contained in:
fourtf 2018-01-17 14:14:31 +01:00
parent 252f648ff8
commit 6d6b99f3ef
15 changed files with 367 additions and 76 deletions

View file

@ -127,7 +127,8 @@ SOURCES += \
src/widgets/settingspages/aboutpage.cpp \ src/widgets/settingspages/aboutpage.cpp \
src/widgets/settingspages/moderationpage.cpp \ src/widgets/settingspages/moderationpage.cpp \
src/widgets/settingspages/logspage.cpp \ src/widgets/settingspages/logspage.cpp \
src/widgets/basewindow.cpp src/widgets/basewindow.cpp \
src/singletons/helper/moderationaction.cpp
HEADERS += \ HEADERS += \
src/precompiled_headers.hpp \ src/precompiled_headers.hpp \
@ -226,7 +227,8 @@ HEADERS += \
src/widgets/settingspages/aboutpage.hpp \ src/widgets/settingspages/aboutpage.hpp \
src/widgets/settingspages/moderationpage.hpp \ src/widgets/settingspages/moderationpage.hpp \
src/widgets/settingspages/logspage.hpp \ src/widgets/settingspages/logspage.hpp \
src/widgets/basewindow.hpp src/widgets/basewindow.hpp \
src/singletons/helper/moderationaction.hpp
PRECOMPILED_HEADER = PRECOMPILED_HEADER =

View file

@ -41,11 +41,22 @@ MessageLayoutElement *MessageLayoutElement::setTrailingSpace(bool value)
return this; return this;
} }
MessageLayoutElement *MessageLayoutElement::setLink(const Link &_link)
{
this->link = _link;
return this;
}
const Link &MessageLayoutElement::getLink() const
{
return this->link;
}
// //
// IMAGE // IMAGE
// //
ImageLayoutElement::ImageLayoutElement(MessageElement &_creator, Image &_image, QSize _size) ImageLayoutElement::ImageLayoutElement(MessageElement &_creator, Image *_image, const QSize &_size)
: MessageLayoutElement(_creator, _size) : MessageLayoutElement(_creator, _size)
, image(_image) , image(_image)
{ {
@ -54,7 +65,7 @@ ImageLayoutElement::ImageLayoutElement(MessageElement &_creator, Image &_image,
void ImageLayoutElement::addCopyTextToString(QString &str, int from, int to) const void ImageLayoutElement::addCopyTextToString(QString &str, int from, int to) const
{ {
str += this->image.getName(); str += this->image->getName();
if (this->hasTrailingSpace()) { if (this->hasTrailingSpace()) {
str += " "; str += " ";
@ -68,9 +79,9 @@ int ImageLayoutElement::getSelectionIndexCount()
void ImageLayoutElement::paint(QPainter &painter) void ImageLayoutElement::paint(QPainter &painter)
{ {
const QPixmap *pixmap = this->image.getPixmap(); const QPixmap *pixmap = this->image->getPixmap();
if (pixmap != nullptr && !this->image.isAnimated()) { if (pixmap != nullptr && !this->image->isAnimated()) {
// fourtf: make it use qreal values // fourtf: make it use qreal values
painter.drawPixmap(QRectF(this->getRect()), *pixmap, QRectF()); painter.drawPixmap(QRectF(this->getRect()), *pixmap, QRectF());
} }
@ -78,12 +89,12 @@ void ImageLayoutElement::paint(QPainter &painter)
void ImageLayoutElement::paintAnimated(QPainter &painter, int yOffset) void ImageLayoutElement::paintAnimated(QPainter &painter, int yOffset)
{ {
if (this->image.isAnimated()) { if (this->image->isAnimated()) {
if (this->image.getPixmap() != nullptr) { if (this->image->getPixmap() != nullptr) {
// fourtf: make it use qreal values // fourtf: make it use qreal values
QRect rect = this->getRect(); QRect rect = this->getRect();
rect.moveTop(rect.y() + yOffset); rect.moveTop(rect.y() + yOffset);
painter.drawPixmap(QRectF(rect), *this->image.getPixmap(), QRectF()); painter.drawPixmap(QRectF(rect), *this->image->getPixmap(), QRectF());
} }
} }
} }
@ -109,7 +120,7 @@ int ImageLayoutElement::getXFromIndex(int index)
// TEXT // TEXT
// //
TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text, QSize _size, TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text, const QSize &_size,
QColor _color, FontStyle _style, float _scale) QColor _color, FontStyle _style, float _scale)
: MessageLayoutElement(_creator, _size) : MessageLayoutElement(_creator, _size)
, text(_text) , text(_text)
@ -188,6 +199,69 @@ int TextLayoutElement::getXFromIndex(int index)
return this->getRect().right(); return this->getRect().right();
} }
} }
// TEXT ICON
TextIconLayoutElement::TextIconLayoutElement(MessageElement &creator, const QString &_line1,
const QString &_line2, float _scale, const QSize &size)
: MessageLayoutElement(creator, size)
, scale(_scale)
, line1(_line1)
, line2(_line2)
{
}
void TextIconLayoutElement::addCopyTextToString(QString &str, int from, int to) const
{
}
int TextIconLayoutElement::getSelectionIndexCount()
{
return this->trailingSpace ? 2 : 1;
}
void TextIconLayoutElement::paint(QPainter &painter)
{
QFont font = singletons::FontManager::getInstance().getFont(FontStyle::Tiny, this->scale);
painter.setBrush(singletons::ThemeManager::getInstance().messages.textColors.regular);
painter.setFont(font);
QTextOption option;
option.setAlignment(Qt::AlignHCenter);
if (this->line2.isEmpty()) {
QRect rect(this->getRect());
painter.drawText(rect, this->line1, option);
} else {
painter.drawText(
QPoint(this->getRect().x(), this->getRect().y() + this->getRect().height() / 2),
this->line1);
painter.drawText(
QPoint(this->getRect().x(), this->getRect().y() + this->getRect().height()),
this->line2);
}
}
void TextIconLayoutElement::paintAnimated(QPainter &painter, int yOffset)
{
}
int TextIconLayoutElement::getMouseOverIndex(const QPoint &abs)
{
return 0;
}
int TextIconLayoutElement::getXFromIndex(int index)
{
if (index <= 0) {
return this->getRect().left();
} else if (index == 1) {
// fourtf: remove space width
return this->getRect().right();
} else {
return this->getRect().right();
}
}
} // namespace layouts } // namespace layouts
} // namespace messages } // namespace messages
} // namespace chatterino } // namespace chatterino

View file

@ -7,6 +7,7 @@
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <climits> #include <climits>
#include "messages/link.hpp"
#include "messages/messagecolor.hpp" #include "messages/messagecolor.hpp"
#include "singletons/fontmanager.hpp" #include "singletons/fontmanager.hpp"
@ -30,6 +31,7 @@ public:
bool hasTrailingSpace() const; bool hasTrailingSpace() const;
MessageLayoutElement *setTrailingSpace(bool value); MessageLayoutElement *setTrailingSpace(bool value);
MessageLayoutElement *setLink(const Link &link);
virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const = 0; virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const = 0;
virtual int getSelectionIndexCount() = 0; virtual int getSelectionIndexCount() = 0;
@ -37,12 +39,14 @@ public:
virtual void paintAnimated(QPainter &painter, int yOffset) = 0; virtual void paintAnimated(QPainter &painter, int yOffset) = 0;
virtual int getMouseOverIndex(const QPoint &abs) = 0; virtual int getMouseOverIndex(const QPoint &abs) = 0;
virtual int getXFromIndex(int index) = 0; virtual int getXFromIndex(int index) = 0;
const Link &getLink() const;
protected: protected:
bool trailingSpace = true; bool trailingSpace = true;
private: private:
QRect rect; QRect rect;
Link link;
// bool isInNewLine; // bool isInNewLine;
MessageElement &creator; MessageElement &creator;
}; };
@ -51,7 +55,7 @@ private:
class ImageLayoutElement : public MessageLayoutElement class ImageLayoutElement : public MessageLayoutElement
{ {
public: public:
ImageLayoutElement(MessageElement &creator, Image &image, QSize size); ImageLayoutElement(MessageElement &creator, Image *image, const QSize &size);
protected: protected:
virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override; virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override;
@ -62,14 +66,14 @@ protected:
virtual int getXFromIndex(int index) override; virtual int getXFromIndex(int index) override;
private: private:
Image &image; Image *image;
}; };
// TEXT // TEXT
class TextLayoutElement : public MessageLayoutElement class TextLayoutElement : public MessageLayoutElement
{ {
public: public:
TextLayoutElement(MessageElement &creator, QString &text, QSize size, QColor color, TextLayoutElement(MessageElement &creator, QString &text, const QSize &size, QColor color,
FontStyle style, float scale); FontStyle style, float scale);
protected: protected:
@ -86,6 +90,28 @@ private:
FontStyle style; FontStyle style;
float scale; float scale;
}; };
// TEXT ICON
// two lines of text (characters) in the size of a normal chat badge
class TextIconLayoutElement : public MessageLayoutElement
{
public:
TextIconLayoutElement(MessageElement &creator, const QString &line1, const QString &line2,
float scale, const QSize &size);
protected:
virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override;
virtual int getSelectionIndexCount() override;
virtual void paint(QPainter &painter) override;
virtual void paintAnimated(QPainter &painter, int yOffset) override;
virtual int getMouseOverIndex(const QPoint &abs) override;
virtual int getXFromIndex(int index) override;
private:
QString line1;
QString line2;
float scale;
};
} // namespace layouts } // namespace layouts
} // namespace messages } // namespace messages
} // namespace chatterino } // namespace chatterino

View file

@ -17,6 +17,7 @@ public:
UserBan, UserBan,
InsertText, InsertText,
ShowMessage, ShowMessage,
UserAction,
}; };
Link(); Link();

View file

@ -52,19 +52,20 @@ MessageElement::Flags MessageElement::getFlags() const
} }
// IMAGE // IMAGE
ImageElement::ImageElement(Image &_image, MessageElement::Flags flags) ImageElement::ImageElement(Image *_image, MessageElement::Flags flags)
: MessageElement(flags) : MessageElement(flags)
, image(_image) , image(_image)
{ {
this->setTooltip(_image.getTooltip()); this->setTooltip(_image->getTooltip());
} }
void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags) void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
{ {
QSize size(this->image.getWidth() * this->image.getScale() * container.scale, QSize size(this->image->getWidth() * this->image->getScale() * container.scale,
this->image.getHeight() * this->image.getScale() * container.scale); this->image->getHeight() * this->image->getScale() * container.scale);
container.addElement(new ImageLayoutElement(*this, this->image, size)); container.addElement(
(new ImageLayoutElement(*this, this->image, size))->setLink(this->getLink()));
} }
void ImageElement::update(UpdateFlags _flags) void ImageElement::update(UpdateFlags _flags)
@ -102,7 +103,7 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElem
QSize size((int)(container.scale * _image->getScaledWidth()), QSize size((int)(container.scale * _image->getScaledWidth()),
(int)(container.scale * _image->getScaledHeight())); (int)(container.scale * _image->getScaledHeight()));
container.addElement(new ImageLayoutElement(*this, *_image, size)); container.addElement((new ImageLayoutElement(*this, _image, size))->setLink(this->getLink()));
} }
void EmoteElement::update(UpdateFlags _flags) void EmoteElement::update(UpdateFlags _flags)
@ -130,9 +131,10 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
for (Word &word : this->words) { for (Word &word : this->words) {
auto getTextLayoutElement = [&](QString text, int width, bool trailingSpace) { auto getTextLayoutElement = [&](QString text, int width, bool trailingSpace) {
auto e = new TextLayoutElement(*this, text, QSize(width, metrics.height()), auto e = (new TextLayoutElement(*this, text, QSize(width, metrics.height()),
this->color.getColor(themeManager), this->style, this->color.getColor(themeManager), this->style,
container.scale); container.scale))
->setLink(this->getLink());
e->setTrailingSpace(trailingSpace); e->setTrailingSpace(trailingSpace);
return e; return e;
}; };
@ -254,6 +256,19 @@ TwitchModerationElement::TwitchModerationElement()
void TwitchModerationElement::addToContainer(MessageLayoutContainer &container, void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
MessageElement::Flags _flags) MessageElement::Flags _flags)
{ {
QSize size((int)(container.scale * 16), (int)(container.scale * 16));
for (const singletons::ModerationAction &m :
singletons::SettingManager::getInstance().getModerationActions()) {
if (m.isImage()) {
container.addElement((new ImageLayoutElement(*this, m.getImage(), size))
->setLink(Link(Link::UserAction, m.getAction())));
} else {
container.addElement((new TextIconLayoutElement(*this, m.getLine1(), m.getLine2(),
container.scale, size))
->setLink(Link(Link::UserAction, m.getAction())));
}
}
} }
void TwitchModerationElement::update(UpdateFlags _flags) void TwitchModerationElement::update(UpdateFlags _flags)

View file

@ -137,10 +137,10 @@ private:
// contains a simple image // contains a simple image
class ImageElement : public MessageElement class ImageElement : public MessageElement
{ {
Image &image; Image *image;
public: public:
ImageElement(Image &image, MessageElement::Flags flags); ImageElement(Image *image, MessageElement::Flags flags);
virtual void addToContainer(MessageLayoutContainer &container, virtual void addToContainer(MessageLayoutContainer &container,
MessageElement::Flags flags) override; MessageElement::Flags flags) override;

View file

@ -27,13 +27,13 @@ FontManager::FontManager()
this->currentFontFamily.connect([this](const std::string &newValue, auto) { this->currentFontFamily.connect([this](const std::string &newValue, auto) {
this->incGeneration(); this->incGeneration();
// this->currentFont.setFamily(newValue.c_str()); // this->currentFont.setFamily(newValue.c_str());
this->currentFontByDpi.clear(); this->currentFontByScale.clear();
this->fontChanged.invoke(); this->fontChanged.invoke();
}); });
this->currentFontSize.connect([this](const int &newValue, auto) { this->currentFontSize.connect([this](const int &newValue, auto) {
this->incGeneration(); this->incGeneration();
// this->currentFont.setSize(newValue); // this->currentFont.setSize(newValue);
this->currentFontByDpi.clear(); this->currentFontByScale.clear();
this->fontChanged.invoke(); this->fontChanged.invoke();
}); });
} }
@ -45,21 +45,23 @@ FontManager &FontManager::getInstance()
return instance; return instance;
} }
QFont &FontManager::getFont(Type type, float dpi) QFont &FontManager::getFont(Type type, float scale)
{ {
// return this->currentFont.getFont(type); // return this->currentFont.getFont(type);
return this->getCurrentFont(dpi).getFont(type); return this->getCurrentFont(scale).getFont(type);
} }
QFontMetrics &FontManager::getFontMetrics(Type type, float dpi) QFontMetrics &FontManager::getFontMetrics(Type type, float scale)
{ {
// return this->currentFont.getFontMetrics(type); // return this->currentFont.getFontMetrics(type);
return this->getCurrentFont(dpi).getFontMetrics(type); return this->getCurrentFont(scale).getFontMetrics(type);
} }
FontManager::FontData &FontManager::Font::getFontData(Type type) FontManager::FontData &FontManager::Font::getFontData(Type type)
{ {
switch (type) { switch (type) {
case Tiny:
return this->tiny;
case Small: case Small:
return this->small; return this->small;
case MediumSmall: case MediumSmall:
@ -90,18 +92,18 @@ QFontMetrics &FontManager::Font::getFontMetrics(Type type)
return this->getFontData(type).metrics; return this->getFontData(type).metrics;
} }
FontManager::Font &FontManager::getCurrentFont(float dpi) FontManager::Font &FontManager::getCurrentFont(float scale)
{ {
for (auto it = this->currentFontByDpi.begin(); it != this->currentFontByDpi.end(); it++) { for (auto it = this->currentFontByScale.begin(); it != this->currentFontByScale.end(); it++) {
if (it->first == dpi) { if (it->first == scale) {
return it->second; return it->second;
} }
} }
this->currentFontByDpi.push_back(std::make_pair( this->currentFontByScale.push_back(
dpi, std::make_pair(scale, Font(this->currentFontFamily.getValue().c_str(),
Font(this->currentFontFamily.getValue().c_str(), this->currentFontSize.getValue() * dpi))); this->currentFontSize.getValue() * scale)));
return this->currentFontByDpi.back().second; return this->currentFontByScale.back().second;
} }
} // namespace singletons
} // namespace chatterino } // namespace chatterino
}

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <QFont> #include <QFont>
#include <QFontDatabase>
#include <QFontMetrics> #include <QFontMetrics>
#include <pajlada/settings/setting.hpp> #include <pajlada/settings/setting.hpp>
#include <pajlada/signals/signal.hpp> #include <pajlada/signals/signal.hpp>
@ -16,6 +17,7 @@ class FontManager
public: public:
enum Type : uint8_t { enum Type : uint8_t {
Tiny,
Small, Small,
MediumSmall, MediumSmall,
Medium, Medium,
@ -28,8 +30,8 @@ public:
// FontManager is initialized only once, on first use // FontManager is initialized only once, on first use
static FontManager &getInstance(); static FontManager &getInstance();
QFont &getFont(Type type, float dpi); QFont &getFont(Type type, float scale);
QFontMetrics &getFontMetrics(Type type, float dpi); QFontMetrics &getFontMetrics(Type type, float scale);
int getGeneration() const int getGeneration() const
{ {
@ -62,7 +64,8 @@ private:
Font() = delete; Font() = delete;
explicit Font(const char *fontFamilyName, int mediumSize) explicit Font(const char *fontFamilyName, int mediumSize)
: small(QFont(fontFamilyName, mediumSize - 4)) : tiny(QFont("Monospace", 8))
, small(QFont(fontFamilyName, mediumSize - 4))
, mediumSmall(QFont(fontFamilyName, mediumSize - 2)) , mediumSmall(QFont(fontFamilyName, mediumSize - 2))
, medium(QFont(fontFamilyName, mediumSize)) , medium(QFont(fontFamilyName, mediumSize))
, mediumBold(QFont(fontFamilyName, mediumSize, QFont::DemiBold)) , mediumBold(QFont(fontFamilyName, mediumSize, QFont::DemiBold))
@ -70,6 +73,7 @@ private:
, large(QFont(fontFamilyName, mediumSize)) , large(QFont(fontFamilyName, mediumSize))
, veryLarge(QFont(fontFamilyName, mediumSize)) , veryLarge(QFont(fontFamilyName, mediumSize))
{ {
tiny.font.setStyleHint(QFont::TypeWriter);
} }
void setFamily(const char *newFamily) void setFamily(const char *newFamily)
@ -114,6 +118,7 @@ private:
QFont &getFont(Type type); QFont &getFont(Type type);
QFontMetrics &getFontMetrics(Type type); QFontMetrics &getFontMetrics(Type type);
FontData tiny;
FontData small; FontData small;
FontData mediumSmall; FontData mediumSmall;
FontData medium; FontData medium;
@ -123,16 +128,16 @@ private:
FontData veryLarge; FontData veryLarge;
}; };
Font &getCurrentFont(float dpi); Font &getCurrentFont(float scale);
// Future plans: // Future plans:
// Could have multiple fonts in here, such as "Menu font", "Application font", "Chat font" // Could have multiple fonts in here, such as "Menu font", "Application font", "Chat font"
std::list<std::pair<float, Font>> currentFontByDpi; std::list<std::pair<float, Font>> currentFontByScale;
int generation = 0; int generation = 0;
}; };
} } // namespace singletons
typedef singletons::FontManager::Type FontStyle; typedef singletons::FontManager::Type FontStyle;
} // namespace chatterino } // namespace chatterino

View file

@ -0,0 +1,50 @@
#include "moderationaction.hpp"
#include "singletons/resourcemanager.hpp"
namespace chatterino {
namespace singletons {
ModerationAction::ModerationAction(messages::Image *_image, const QString &_action)
: _isImage(true)
, image(_image)
, action(_action)
{
}
ModerationAction::ModerationAction(const QString &_line1, const QString &_line2,
const QString &_action)
: _isImage(false)
, image(nullptr)
, line1(_line1)
, line2(_line2)
, action(_action)
{
}
bool ModerationAction::isImage() const
{
return this->_isImage;
}
messages::Image *ModerationAction::getImage() const
{
return this->image;
}
const QString &ModerationAction::getLine1() const
{
return this->line1;
}
const QString &ModerationAction::getLine2() const
{
return this->line2;
}
const QString &ModerationAction::getAction() const
{
return this->action;
}
} // namespace singletons
} // namespace chatterino

View file

@ -0,0 +1,31 @@
#pragma once
#include <QString>
namespace chatterino {
namespace messages {
class Image;
}
namespace singletons {
class ModerationAction
{
public:
ModerationAction(messages::Image *image, const QString &action);
ModerationAction(const QString &line1, const QString &line2, const QString &action);
bool isImage() const;
messages::Image *getImage() const;
const QString &getLine1() const;
const QString &getLine2() const;
const QString &getAction() const;
private:
bool _isImage;
messages::Image *image;
QString line1;
QString line2;
QString action;
};
} // namespace singletons
} // namespace chatterino

View file

@ -1,6 +1,7 @@
#include "singletons/settingsmanager.hpp" #include "singletons/settingsmanager.hpp"
#include "debug/log.hpp" #include "debug/log.hpp"
#include "singletons/pathmanager.hpp" #include "singletons/pathmanager.hpp"
#include "singletons/resourcemanager.hpp"
using namespace chatterino::messages; using namespace chatterino::messages;
@ -26,6 +27,8 @@ SettingManager::SettingManager()
this->wordMaskListener.cb = [this](auto) { this->wordMaskListener.cb = [this](auto) {
this->updateWordTypeMask(); // this->updateWordTypeMask(); //
}; };
this->moderationActions.connect([this](auto, auto) { this->updateModerationActions(); });
} }
MessageElement::Flags SettingManager::getWordTypeMask() MessageElement::Flags SettingManager::getWordTypeMask()
@ -127,5 +130,77 @@ void SettingManager::recallSnapshot()
} }
} }
std::vector<ModerationAction> SettingManager::getModerationActions() const
{
return this->_moderationActions;
}
void SettingManager::updateModerationActions()
{
auto &resources = singletons::ResourceManager::getInstance();
this->_moderationActions.clear();
static QRegularExpression newLineRegex("(\r\n?|\n)+");
static QRegularExpression replaceRegex("[!/.]");
static QRegularExpression timeoutRegex("^[./]timeout.* (\\d+)");
QStringList list = this->moderationActions.getValue().split(newLineRegex);
int multipleTimeouts = 0;
for (QString &str : list) {
if (timeoutRegex.match(str).hasMatch()) {
multipleTimeouts++;
if (multipleTimeouts > 1) {
break;
}
}
}
for (int i = 0; i < list.size(); i++) {
QString &str = list[i];
if (str.isEmpty()) {
continue;
}
auto timeoutMatch = timeoutRegex.match(str);
if (timeoutMatch.hasMatch()) {
if (multipleTimeouts > 1) {
QString line1;
QString line2;
int amount = timeoutMatch.captured(1).toInt();
if (amount < 60) {
line1 = QString::number(amount);
line2 = "s";
} else if (amount < 60 * 60) {
line1 = QString::number(amount / 60);
line2 = "m";
} else if (amount < 60 * 60 * 24) {
line1 = QString::number(amount / 60 / 60);
line2 = "h";
} else {
line1 = QString::number(amount / 60 / 60 / 24);
line2 = "d";
}
this->_moderationActions.emplace_back(line1, line2, str);
} else {
this->_moderationActions.emplace_back(resources.buttonTimeout, str);
}
} else if (str.startsWith("/ban ")) {
this->_moderationActions.emplace_back(resources.buttonBan, str);
} else {
QString xD = str;
xD.replace(replaceRegex, "");
this->_moderationActions.emplace_back(xD.mid(0, 2), xD.mid(2, 2), str);
}
}
}
} // namespace singletons } // namespace singletons
} // namespace chatterino } // namespace chatterino

View file

@ -3,6 +3,7 @@
#include "messages/highlightphrase.hpp" #include "messages/highlightphrase.hpp"
#include "messages/messageelement.hpp" #include "messages/messageelement.hpp"
#include "singletons/helper/chatterinosetting.hpp" #include "singletons/helper/chatterinosetting.hpp"
#include "singletons/helper/moderationaction.hpp"
#include <pajlada/settings/setting.hpp> #include <pajlada/settings/setting.hpp>
#include <pajlada/settings/settinglistener.hpp> #include <pajlada/settings/settinglistener.hpp>
@ -105,14 +106,19 @@ public:
void saveSnapshot(); void saveSnapshot();
void recallSnapshot(); void recallSnapshot();
std::vector<ModerationAction> getModerationActions() const;
signals: signals:
void wordTypeMaskChanged(); void wordTypeMaskChanged();
private: private:
std::vector<ModerationAction> _moderationActions;
std::unique_ptr<rapidjson::Document> snapshot; std::unique_ptr<rapidjson::Document> snapshot;
SettingManager(); SettingManager();
void updateModerationActions();
messages::MessageElement::Flags wordTypeMask = messages::MessageElement::Default; messages::MessageElement::Flags wordTypeMask = messages::MessageElement::Default;
pajlada::Settings::SettingListener wordMaskListener; pajlada::Settings::SettingListener wordMaskListener;

View file

@ -511,7 +511,7 @@ void TwitchMessageBuilder::parseTwitchBadges()
// Try to fetch channel-specific bit badge // Try to fetch channel-specific bit badge
try { try {
const auto &badge = channelResources.badgeSets.at("bits").versions.at(versionKey); const auto &badge = channelResources.badgeSets.at("bits").versions.at(versionKey);
this->append<ImageElement>(*(badge.badgeImage1x), MessageElement::BadgeVanity); this->append<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity);
continue; continue;
} catch (const std::out_of_range &) { } catch (const std::out_of_range &) {
// Channel does not contain a special bit badge for this version // Channel does not contain a special bit badge for this version
@ -520,44 +520,44 @@ void TwitchMessageBuilder::parseTwitchBadges()
// Use default bit badge // Use default bit badge
try { try {
const auto &badge = resourceManager.badgeSets.at("bits").versions.at(versionKey); const auto &badge = resourceManager.badgeSets.at("bits").versions.at(versionKey);
this->append<ImageElement>(*(badge.badgeImage1x), MessageElement::BadgeVanity); this->append<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity);
} catch (const std::out_of_range &) { } catch (const std::out_of_range &) {
debug::Log("No default bit badge for version {} found", versionKey); debug::Log("No default bit badge for version {} found", versionKey);
continue; continue;
} }
} else if (badge == "staff/1") { } else if (badge == "staff/1") {
this->append<ImageElement>(*resourceManager.badgeStaff, this->append<ImageElement>(resourceManager.badgeStaff,
MessageElement::BadgeGlobalAuthority) MessageElement::BadgeGlobalAuthority)
->setTooltip("Twitch Staff"); ->setTooltip("Twitch Staff");
} else if (badge == "admin/1") { } else if (badge == "admin/1") {
this->append<ImageElement>(*resourceManager.badgeAdmin, this->append<ImageElement>(resourceManager.badgeAdmin,
MessageElement::BadgeGlobalAuthority) MessageElement::BadgeGlobalAuthority)
->setTooltip("Twitch Admin"); ->setTooltip("Twitch Admin");
} else if (badge == "global_mod/1") { } else if (badge == "global_mod/1") {
this->append<ImageElement>(*resourceManager.badgeGlobalModerator, this->append<ImageElement>(resourceManager.badgeGlobalModerator,
MessageElement::BadgeGlobalAuthority) MessageElement::BadgeGlobalAuthority)
->setTooltip("Twitch Global Moderator"); ->setTooltip("Twitch Global Moderator");
} else if (badge == "moderator/1") { } else if (badge == "moderator/1") {
// TODO: Implement custom FFZ moderator badge // TODO: Implement custom FFZ moderator badge
this->append<ImageElement>(*resourceManager.badgeModerator, this->append<ImageElement>(resourceManager.badgeModerator,
MessageElement::BadgeChannelAuthority) MessageElement::BadgeChannelAuthority)
->setTooltip("Twitch Channel Moderator"); ->setTooltip("Twitch Channel Moderator");
} else if (badge == "turbo/1") { } else if (badge == "turbo/1") {
this->append<ImageElement>(*resourceManager.badgeTurbo, this->append<ImageElement>(resourceManager.badgeTurbo,
MessageElement::BadgeGlobalAuthority) MessageElement::BadgeGlobalAuthority)
->setTooltip("Twitch Turbo Subscriber"); ->setTooltip("Twitch Turbo Subscriber");
} else if (badge == "broadcaster/1") { } else if (badge == "broadcaster/1") {
this->append<ImageElement>(*resourceManager.badgeBroadcaster, this->append<ImageElement>(resourceManager.badgeBroadcaster,
MessageElement::BadgeChannelAuthority) MessageElement::BadgeChannelAuthority)
->setTooltip("Twitch Broadcaster"); ->setTooltip("Twitch Broadcaster");
} else if (badge == "premium/1") { } else if (badge == "premium/1") {
this->append<ImageElement>(*resourceManager.badgePremium, MessageElement::BadgeVanity) this->append<ImageElement>(resourceManager.badgePremium, MessageElement::BadgeVanity)
->setTooltip("Twitch Prime Subscriber"); ->setTooltip("Twitch Prime Subscriber");
} else if (badge.startsWith("partner/")) { } else if (badge.startsWith("partner/")) {
int index = badge.midRef(8).toInt(); int index = badge.midRef(8).toInt();
switch (index) { switch (index) {
case 1: { case 1: {
this->append<ImageElement>(*resourceManager.badgeVerified, this->append<ImageElement>(resourceManager.badgeVerified,
MessageElement::BadgeVanity) MessageElement::BadgeVanity)
->setTooltip("Twitch Verified"); ->setTooltip("Twitch Verified");
} break; } break;
@ -574,7 +574,7 @@ void TwitchMessageBuilder::parseTwitchBadges()
auto badgeSetIt = channelResources.badgeSets.find("subscriber"); auto badgeSetIt = channelResources.badgeSets.find("subscriber");
if (badgeSetIt == channelResources.badgeSets.end()) { if (badgeSetIt == channelResources.badgeSets.end()) {
// Fall back to default badge // Fall back to default badge
this->append<ImageElement>(*resourceManager.badgeSubscriber, this->append<ImageElement>(resourceManager.badgeSubscriber,
MessageElement::BadgeSubscription) MessageElement::BadgeSubscription)
->setTooltip("Twitch Subscriber"); ->setTooltip("Twitch Subscriber");
continue; continue;
@ -588,7 +588,7 @@ void TwitchMessageBuilder::parseTwitchBadges()
if (badgeVersionIt == badgeSet.versions.end()) { if (badgeVersionIt == badgeSet.versions.end()) {
// Fall back to default badge // Fall back to default badge
this->append<ImageElement>(*resourceManager.badgeSubscriber, this->append<ImageElement>(resourceManager.badgeSubscriber,
MessageElement::BadgeSubscription) MessageElement::BadgeSubscription)
->setTooltip("Twitch Subscriber"); ->setTooltip("Twitch Subscriber");
continue; continue;
@ -596,8 +596,7 @@ void TwitchMessageBuilder::parseTwitchBadges()
auto &badgeVersion = badgeVersionIt->second; auto &badgeVersion = badgeVersionIt->second;
this->append<ImageElement>(*badgeVersion.badgeImage1x, this->append<ImageElement>(badgeVersion.badgeImage1x, MessageElement::BadgeSubscription)
MessageElement::BadgeSubscription)
->setTooltip("Twitch " + QString::fromStdString(badgeVersion.title)); ->setTooltip("Twitch " + QString::fromStdString(badgeVersion.title));
} else { } else {
if (!resourceManager.dynamicBadgesLoaded) { if (!resourceManager.dynamicBadgesLoaded) {
@ -623,7 +622,7 @@ void TwitchMessageBuilder::parseTwitchBadges()
try { try {
auto &badgeVersion = badgeSet.versions.at(versionKey); auto &badgeVersion = badgeSet.versions.at(versionKey);
this->append<ImageElement>(*badgeVersion.badgeImage1x, badgeType) this->append<ImageElement>(badgeVersion.badgeImage1x, badgeType)
->setTooltip("Twitch " + QString::fromStdString(badgeVersion.title)); ->setTooltip("Twitch " + QString::fromStdString(badgeVersion.title));
} catch (const std::exception &e) { } catch (const std::exception &e) {
qDebug() << "Exception caught:" << e.what() qDebug() << "Exception caught:" << e.what()
@ -648,7 +647,7 @@ void TwitchMessageBuilder::addChatterinoBadges()
const auto badge = it->second; const auto badge = it->second;
this->append<ImageElement>(*badge->image, MessageElement::BadgeChatterino) this->append<ImageElement>(badge->image, MessageElement::BadgeChatterino)
->setTooltip(QString::fromStdString(badge->tooltip)); ->setTooltip(QString::fromStdString(badge->tooltip));
} }

View file

@ -641,7 +641,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
} }
// check if word has a link // check if word has a link
if (hoverLayoutElement->getCreator().getLink().isValid()) { if (hoverLayoutElement->getLink().isValid()) {
this->setCursor(Qt::PointingHandCursor); this->setCursor(Qt::PointingHandCursor);
} else { } else {
this->setCursor(Qt::ArrowCursor); this->setCursor(Qt::ArrowCursor);
@ -764,7 +764,7 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
return; return;
} }
auto &link = hoverLayoutElement->getCreator().getLink(); auto &link = hoverLayoutElement->getLink();
switch (link.getType()) { switch (link.getType()) {
case messages::Link::UserInfo: { case messages::Link::UserInfo: {
@ -782,6 +782,11 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
QDesktopServices::openUrl(QUrl(link.getValue())); QDesktopServices::openUrl(QUrl(link.getValue()));
break; break;
} }
case messages::Link::UserAction: {
QString value = link.getValue();
value.replace("{user}", layout->getMessage()->loginName);
this->channel->sendMessage(value);
}
} }
} }

View file

@ -21,21 +21,21 @@ ModerationPage::ModerationPage()
auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin(); auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
{ {
// clang-format off // clang-format off
auto label = layout.emplace<QLabel>("In channels that you moderate there is a button <insert image of button here> to enable moderation mode."); auto label = layout.emplace<QLabel>("In channels that you moderate there is a button <insert image of button here> to enable moderation mode.\n\nOne action per line. {user} will be replaced with the username.\nExample `/timeout {user} 120`");
label->setWordWrap(true); label->setWordWrap(true);
// clang-format on // clang-format on
auto text = layout.emplace<QTextEdit>().getElement(); auto text = layout.emplace<QTextEdit>().getElement();
settings.moderationActions.connect([=](const QString &str, auto) {
text->setPlainText(str); //
});
QObject::connect(text, &QTextEdit::textChanged, this, QObject::connect(text, &QTextEdit::textChanged, this,
[this] { this->itemsChangedTimer.start(200); }); [this] { this->itemsChangedTimer.start(200); });
QObject::connect(&this->itemsChangedTimer, &QTimer::timeout, this, QObject::connect(&this->itemsChangedTimer, &QTimer::timeout, this,
[text, &settings]() { settings.moderationActions = text->toPlainText(); }); [text, &settings]() { settings.moderationActions = text->toPlainText(); });
settings.highlightUserBlacklist.connect([=](const QString &str, auto) {
text->setPlainText(str); //
});
} }
// ---- misc // ---- misc