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/moderationpage.cpp \
src/widgets/settingspages/logspage.cpp \
src/widgets/basewindow.cpp
src/widgets/basewindow.cpp \
src/singletons/helper/moderationaction.cpp
HEADERS += \
src/precompiled_headers.hpp \
@ -226,7 +227,8 @@ HEADERS += \
src/widgets/settingspages/aboutpage.hpp \
src/widgets/settingspages/moderationpage.hpp \
src/widgets/settingspages/logspage.hpp \
src/widgets/basewindow.hpp
src/widgets/basewindow.hpp \
src/singletons/helper/moderationaction.hpp
PRECOMPILED_HEADER =

View file

@ -41,11 +41,22 @@ MessageLayoutElement *MessageLayoutElement::setTrailingSpace(bool value)
return this;
}
MessageLayoutElement *MessageLayoutElement::setLink(const Link &_link)
{
this->link = _link;
return this;
}
const Link &MessageLayoutElement::getLink() const
{
return this->link;
}
//
// IMAGE
//
ImageLayoutElement::ImageLayoutElement(MessageElement &_creator, Image &_image, QSize _size)
ImageLayoutElement::ImageLayoutElement(MessageElement &_creator, Image *_image, const QSize &_size)
: MessageLayoutElement(_creator, _size)
, image(_image)
{
@ -54,7 +65,7 @@ ImageLayoutElement::ImageLayoutElement(MessageElement &_creator, Image &_image,
void ImageLayoutElement::addCopyTextToString(QString &str, int from, int to) const
{
str += this->image.getName();
str += this->image->getName();
if (this->hasTrailingSpace()) {
str += " ";
@ -68,9 +79,9 @@ int ImageLayoutElement::getSelectionIndexCount()
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
painter.drawPixmap(QRectF(this->getRect()), *pixmap, QRectF());
}
@ -78,12 +89,12 @@ void ImageLayoutElement::paint(QPainter &painter)
void ImageLayoutElement::paintAnimated(QPainter &painter, int yOffset)
{
if (this->image.isAnimated()) {
if (this->image.getPixmap() != nullptr) {
if (this->image->isAnimated()) {
if (this->image->getPixmap() != nullptr) {
// fourtf: make it use qreal values
QRect rect = this->getRect();
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
//
TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text, QSize _size,
TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text, const QSize &_size,
QColor _color, FontStyle _style, float _scale)
: MessageLayoutElement(_creator, _size)
, text(_text)
@ -188,6 +199,69 @@ int TextLayoutElement::getXFromIndex(int index)
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 messages
} // namespace chatterino

View file

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

View file

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

View file

@ -52,19 +52,20 @@ MessageElement::Flags MessageElement::getFlags() const
}
// IMAGE
ImageElement::ImageElement(Image &_image, MessageElement::Flags flags)
ImageElement::ImageElement(Image *_image, MessageElement::Flags flags)
: MessageElement(flags)
, image(_image)
{
this->setTooltip(_image.getTooltip());
this->setTooltip(_image->getTooltip());
}
void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
{
QSize size(this->image.getWidth() * this->image.getScale() * container.scale,
this->image.getHeight() * this->image.getScale() * container.scale);
QSize size(this->image->getWidth() * 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)
@ -102,7 +103,7 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElem
QSize size((int)(container.scale * _image->getScaledWidth()),
(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)
@ -130,9 +131,10 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
for (Word &word : this->words) {
auto getTextLayoutElement = [&](QString text, int width, bool trailingSpace) {
auto e = new TextLayoutElement(*this, text, QSize(width, metrics.height()),
this->color.getColor(themeManager), this->style,
container.scale);
auto e = (new TextLayoutElement(*this, text, QSize(width, metrics.height()),
this->color.getColor(themeManager), this->style,
container.scale))
->setLink(this->getLink());
e->setTrailingSpace(trailingSpace);
return e;
};
@ -254,6 +256,19 @@ TwitchModerationElement::TwitchModerationElement()
void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
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)

View file

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

View file

@ -4,16 +4,16 @@
#include <QtGlobal>
#ifdef Q_OS_WIN32
#define DEFAULT_FONT_FAMILY "Segoe UI"
#define DEFAULT_FONT_SIZE 10
#define DEFAULT_FONT_FAMILY "Segoe UI"
#define DEFAULT_FONT_SIZE 10
#else
#ifdef Q_OS_MACOS
#define DEFAULT_FONT_FAMILY "Helvetica Neue"
#define DEFAULT_FONT_SIZE 12
#else
#define DEFAULT_FONT_FAMILY "Arial"
#define DEFAULT_FONT_SIZE 11
#endif
#ifdef Q_OS_MACOS
#define DEFAULT_FONT_FAMILY "Helvetica Neue"
#define DEFAULT_FONT_SIZE 12
#else
#define DEFAULT_FONT_FAMILY "Arial"
#define DEFAULT_FONT_SIZE 11
#endif
#endif
namespace chatterino {
@ -27,13 +27,13 @@ FontManager::FontManager()
this->currentFontFamily.connect([this](const std::string &newValue, auto) {
this->incGeneration();
// this->currentFont.setFamily(newValue.c_str());
this->currentFontByDpi.clear();
this->currentFontByScale.clear();
this->fontChanged.invoke();
});
this->currentFontSize.connect([this](const int &newValue, auto) {
this->incGeneration();
// this->currentFont.setSize(newValue);
this->currentFontByDpi.clear();
this->currentFontByScale.clear();
this->fontChanged.invoke();
});
}
@ -45,21 +45,23 @@ FontManager &FontManager::getInstance()
return instance;
}
QFont &FontManager::getFont(Type type, float dpi)
QFont &FontManager::getFont(Type type, float scale)
{
// 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->getCurrentFont(dpi).getFontMetrics(type);
return this->getCurrentFont(scale).getFontMetrics(type);
}
FontManager::FontData &FontManager::Font::getFontData(Type type)
{
switch (type) {
case Tiny:
return this->tiny;
case Small:
return this->small;
case MediumSmall:
@ -90,18 +92,18 @@ QFontMetrics &FontManager::Font::getFontMetrics(Type type)
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++) {
if (it->first == dpi) {
for (auto it = this->currentFontByScale.begin(); it != this->currentFontByScale.end(); it++) {
if (it->first == scale) {
return it->second;
}
}
this->currentFontByDpi.push_back(std::make_pair(
dpi,
Font(this->currentFontFamily.getValue().c_str(), this->currentFontSize.getValue() * dpi)));
this->currentFontByScale.push_back(
std::make_pair(scale, Font(this->currentFontFamily.getValue().c_str(),
this->currentFontSize.getValue() * scale)));
return this->currentFontByDpi.back().second;
return this->currentFontByScale.back().second;
}
} // namespace singletons
} // namespace chatterino
}

View file

@ -1,6 +1,7 @@
#pragma once
#include <QFont>
#include <QFontDatabase>
#include <QFontMetrics>
#include <pajlada/settings/setting.hpp>
#include <pajlada/signals/signal.hpp>
@ -16,6 +17,7 @@ class FontManager
public:
enum Type : uint8_t {
Tiny,
Small,
MediumSmall,
Medium,
@ -28,8 +30,8 @@ public:
// FontManager is initialized only once, on first use
static FontManager &getInstance();
QFont &getFont(Type type, float dpi);
QFontMetrics &getFontMetrics(Type type, float dpi);
QFont &getFont(Type type, float scale);
QFontMetrics &getFontMetrics(Type type, float scale);
int getGeneration() const
{
@ -62,7 +64,8 @@ private:
Font() = delete;
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))
, medium(QFont(fontFamilyName, mediumSize))
, mediumBold(QFont(fontFamilyName, mediumSize, QFont::DemiBold))
@ -70,6 +73,7 @@ private:
, large(QFont(fontFamilyName, mediumSize))
, veryLarge(QFont(fontFamilyName, mediumSize))
{
tiny.font.setStyleHint(QFont::TypeWriter);
}
void setFamily(const char *newFamily)
@ -114,6 +118,7 @@ private:
QFont &getFont(Type type);
QFontMetrics &getFontMetrics(Type type);
FontData tiny;
FontData small;
FontData mediumSmall;
FontData medium;
@ -123,16 +128,16 @@ private:
FontData veryLarge;
};
Font &getCurrentFont(float dpi);
Font &getCurrentFont(float scale);
// Future plans:
// 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;
};
}
} // namespace singletons
typedef singletons::FontManager::Type FontStyle;
} // 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 "debug/log.hpp"
#include "singletons/pathmanager.hpp"
#include "singletons/resourcemanager.hpp"
using namespace chatterino::messages;
@ -26,6 +27,8 @@ SettingManager::SettingManager()
this->wordMaskListener.cb = [this](auto) {
this->updateWordTypeMask(); //
};
this->moderationActions.connect([this](auto, auto) { this->updateModerationActions(); });
}
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 chatterino

View file

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

View file

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

View file

@ -641,7 +641,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
}
// check if word has a link
if (hoverLayoutElement->getCreator().getLink().isValid()) {
if (hoverLayoutElement->getLink().isValid()) {
this->setCursor(Qt::PointingHandCursor);
} else {
this->setCursor(Qt::ArrowCursor);
@ -764,7 +764,7 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
return;
}
auto &link = hoverLayoutElement->getCreator().getLink();
auto &link = hoverLayoutElement->getLink();
switch (link.getType()) {
case messages::Link::UserInfo: {
@ -782,6 +782,11 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
QDesktopServices::openUrl(QUrl(link.getValue()));
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();
{
// 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);
// clang-format on
auto text = layout.emplace<QTextEdit>().getElement();
settings.moderationActions.connect([=](const QString &str, auto) {
text->setPlainText(str); //
});
QObject::connect(text, &QTextEdit::textChanged, this,
[this] { this->itemsChangedTimer.start(200); });
QObject::connect(&this->itemsChangedTimer, &QTimer::timeout, this,
[text, &settings]() { settings.moderationActions = text->toPlainText(); });
settings.highlightUserBlacklist.connect([=](const QString &str, auto) {
text->setPlainText(str); //
});
}
// ---- misc