Merge branch 'master' into apa-bits
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/Toasts.hpp"
|
||||
#include "singletons/Updates.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/IsBigEndian.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
@@ -99,6 +100,9 @@ int Application::run(QApplication &qtApp)
|
||||
|
||||
this->windows->getMainWindow().show();
|
||||
|
||||
getSettings()->betaUpdates.connect(
|
||||
[] { Updates::getInstance().checkForUpdates(); }, false);
|
||||
|
||||
return qtApp.exec();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
class Channel;
|
||||
class ChannelView;
|
||||
using ChannelPtr = std::shared_ptr<Channel>;
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
} // namespace chatterino
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
# include <fmt/format.h>
|
||||
# include <irccommand.h>
|
||||
|
||||
@@ -10,6 +10,9 @@ Resources2::Resources2()
|
||||
this->buttons.addSplitDark = QPixmap(":/buttons/addSplitDark.png");
|
||||
this->buttons.ban = QPixmap(":/buttons/ban.png");
|
||||
this->buttons.banRed = QPixmap(":/buttons/banRed.png");
|
||||
this->buttons.copyDark = QPixmap(":/buttons/copyDark.png");
|
||||
this->buttons.copyDarkTheme = QPixmap(":/buttons/copyDarkTheme.png");
|
||||
this->buttons.copyLight = QPixmap(":/buttons/copyLight.png");
|
||||
this->buttons.menuDark = QPixmap(":/buttons/menuDark.png");
|
||||
this->buttons.menuLight = QPixmap(":/buttons/menuLight.png");
|
||||
this->buttons.mod = QPixmap(":/buttons/mod.png");
|
||||
@@ -17,6 +20,7 @@ Resources2::Resources2()
|
||||
this->buttons.modModeDisabled2 = QPixmap(":/buttons/modModeDisabled2.png");
|
||||
this->buttons.modModeEnabled = QPixmap(":/buttons/modModeEnabled.png");
|
||||
this->buttons.modModeEnabled2 = QPixmap(":/buttons/modModeEnabled2.png");
|
||||
this->buttons.search = QPixmap(":/buttons/search.png");
|
||||
this->buttons.timeout = QPixmap(":/buttons/timeout.png");
|
||||
this->buttons.trashCan = QPixmap(":/buttons/trashCan.png");
|
||||
this->buttons.unban = QPixmap(":/buttons/unban.png");
|
||||
@@ -46,4 +50,4 @@ Resources2::Resources2()
|
||||
this->twitch.vip = QPixmap(":/twitch/vip.png");
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
} // namespace chatterino
|
||||
@@ -17,6 +17,9 @@ public:
|
||||
QPixmap addSplitDark;
|
||||
QPixmap ban;
|
||||
QPixmap banRed;
|
||||
QPixmap copyDark;
|
||||
QPixmap copyDarkTheme;
|
||||
QPixmap copyLight;
|
||||
QPixmap menuDark;
|
||||
QPixmap menuLight;
|
||||
QPixmap mod;
|
||||
@@ -24,6 +27,7 @@ public:
|
||||
QPixmap modModeDisabled2;
|
||||
QPixmap modModeEnabled;
|
||||
QPixmap modModeEnabled2;
|
||||
QPixmap search;
|
||||
QPixmap timeout;
|
||||
QPixmap trashCan;
|
||||
QPixmap unban;
|
||||
@@ -60,4 +64,4 @@ public:
|
||||
} twitch;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
} // namespace chatterino
|
||||
@@ -24,6 +24,17 @@ QJsonObject NetworkResult::parseJson() const
|
||||
return jsonDoc.object();
|
||||
}
|
||||
|
||||
QJsonArray NetworkResult::parseJsonArray() const
|
||||
{
|
||||
QJsonDocument jsonDoc(QJsonDocument::fromJson(this->data_));
|
||||
if (jsonDoc.isNull())
|
||||
{
|
||||
return QJsonArray{};
|
||||
}
|
||||
|
||||
return jsonDoc.array();
|
||||
}
|
||||
|
||||
rapidjson::Document NetworkResult::parseRapidJson() const
|
||||
{
|
||||
rapidjson::Document ret(rapidjson::kObjectType);
|
||||
|
||||
@@ -10,7 +10,13 @@ class NetworkResult
|
||||
public:
|
||||
NetworkResult(const QByteArray &data);
|
||||
|
||||
/// Parses the result as json and returns the root as an object.
|
||||
/// Returns empty object if parsing failed.
|
||||
QJsonObject parseJson() const;
|
||||
/// Parses the result as json and returns the root as an array.
|
||||
/// Returns empty object if parsing failed.
|
||||
QJsonArray parseJsonArray() const;
|
||||
/// Parses the result as json and returns the document.
|
||||
rapidjson::Document parseRapidJson() const;
|
||||
const QByteArray &getData() const;
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ public:
|
||||
return this->vector_;
|
||||
}
|
||||
|
||||
const std::vector<TVectorItem> cloneVector() const
|
||||
std::vector<TVectorItem> cloneVector() const
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#define CHATTERINO_VERSION "2.1.2"
|
||||
#define CHATTERINO_VERSION "2.1.4-beta-1"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
# define CHATTERINO_OS "win"
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace chatterino {
|
||||
|
||||
// commandmodel
|
||||
HighlightModel::HighlightModel(QObject *parent)
|
||||
: SignalVectorModel<HighlightPhrase>(4, parent)
|
||||
: SignalVectorModel<HighlightPhrase>(5, parent)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -16,12 +16,13 @@ HighlightModel::HighlightModel(QObject *parent)
|
||||
HighlightPhrase HighlightModel::getItemFromRow(
|
||||
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
||||
{
|
||||
// key, alert, sound, regex
|
||||
// key, alert, sound, regex, case-sensitivity
|
||||
|
||||
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[3]->data(Qt::CheckStateRole).toBool(),
|
||||
row[4]->data(Qt::CheckStateRole).toBool()};
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
@@ -32,6 +33,7 @@ void HighlightModel::getRowFromItem(const HighlightPhrase &item,
|
||||
setBoolItem(row[1], item.getAlert());
|
||||
setBoolItem(row[2], item.getSound());
|
||||
setBoolItem(row[3], item.isRegex());
|
||||
setBoolItem(row[4], item.isCaseSensitive());
|
||||
}
|
||||
|
||||
void HighlightModel::afterInit()
|
||||
|
||||
@@ -15,21 +15,24 @@ public:
|
||||
bool operator==(const HighlightPhrase &other) const
|
||||
{
|
||||
return std::tie(this->pattern_, this->sound_, this->alert_,
|
||||
this->isRegex_) == std::tie(other.pattern_,
|
||||
other.sound_, other.alert_,
|
||||
other.isRegex_);
|
||||
this->isRegex_, this->caseSensitive_) ==
|
||||
std::tie(other.pattern_, other.sound_, other.alert_,
|
||||
other.isRegex_, other.caseSensitive_);
|
||||
}
|
||||
|
||||
HighlightPhrase(const QString &pattern, bool alert, bool sound,
|
||||
bool isRegex)
|
||||
bool isRegex, bool caseSensitive)
|
||||
: pattern_(pattern)
|
||||
, alert_(alert)
|
||||
, sound_(sound)
|
||||
, isRegex_(isRegex)
|
||||
, regex_(isRegex_ ? pattern
|
||||
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
||||
QRegularExpression::CaseInsensitiveOption |
|
||||
QRegularExpression::UseUnicodePropertiesOption)
|
||||
, caseSensitive_(caseSensitive)
|
||||
, regex_(
|
||||
isRegex_ ? pattern
|
||||
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
||||
QRegularExpression::UseUnicodePropertiesOption |
|
||||
(caseSensitive_ ? QRegularExpression::NoPatternOption
|
||||
: QRegularExpression::CaseInsensitiveOption))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -60,11 +63,17 @@ public:
|
||||
return this->isValid() && this->regex_.match(subject).hasMatch();
|
||||
}
|
||||
|
||||
bool isCaseSensitive() const
|
||||
{
|
||||
return this->caseSensitive_;
|
||||
}
|
||||
|
||||
private:
|
||||
QString pattern_;
|
||||
bool alert_;
|
||||
bool sound_;
|
||||
bool isRegex_;
|
||||
bool caseSensitive_;
|
||||
QRegularExpression regex_;
|
||||
};
|
||||
} // namespace chatterino
|
||||
@@ -82,6 +91,7 @@ struct Serialize<chatterino::HighlightPhrase> {
|
||||
chatterino::rj::set(ret, "alert", value.getAlert(), a);
|
||||
chatterino::rj::set(ret, "sound", value.getSound(), a);
|
||||
chatterino::rj::set(ret, "regex", value.isRegex(), a);
|
||||
chatterino::rj::set(ret, "case", value.isCaseSensitive(), a);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -93,20 +103,24 @@ struct Deserialize<chatterino::HighlightPhrase> {
|
||||
{
|
||||
if (!value.IsObject())
|
||||
{
|
||||
return chatterino::HighlightPhrase(QString(), true, false, false);
|
||||
return chatterino::HighlightPhrase(QString(), true, false, false,
|
||||
false);
|
||||
}
|
||||
|
||||
QString _pattern;
|
||||
bool _alert = true;
|
||||
bool _sound = false;
|
||||
bool _isRegex = false;
|
||||
bool _caseSensitive = false;
|
||||
|
||||
chatterino::rj::getSafe(value, "pattern", _pattern);
|
||||
chatterino::rj::getSafe(value, "alert", _alert);
|
||||
chatterino::rj::getSafe(value, "sound", _sound);
|
||||
chatterino::rj::getSafe(value, "regex", _isRegex);
|
||||
chatterino::rj::getSafe(value, "case", _caseSensitive);
|
||||
|
||||
return chatterino::HighlightPhrase(_pattern, _alert, _sound, _isRegex);
|
||||
return chatterino::HighlightPhrase(_pattern, _alert, _sound,
|
||||
_isRegex, _caseSensitive);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace chatterino {
|
||||
|
||||
// commandmodel
|
||||
UserHighlightModel::UserHighlightModel(QObject *parent)
|
||||
: SignalVectorModel<HighlightPhrase>(4, parent)
|
||||
: SignalVectorModel<HighlightPhrase>(5, parent)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ HighlightPhrase UserHighlightModel::getItemFromRow(
|
||||
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[3]->data(Qt::CheckStateRole).toBool(),
|
||||
row[4]->data(Qt::CheckStateRole).toBool()};
|
||||
}
|
||||
|
||||
// row into vector item
|
||||
@@ -32,6 +33,7 @@ void UserHighlightModel::getRowFromItem(const HighlightPhrase &item,
|
||||
setBoolItem(row[1], item.getAlert());
|
||||
setBoolItem(row[2], item.getSound());
|
||||
setBoolItem(row[3], item.isRegex());
|
||||
setBoolItem(row[4], item.isCaseSensitive());
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -22,7 +22,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
std::size_t size()
|
||||
std::size_t size() const
|
||||
{
|
||||
return this->length_;
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
|
||||
QSize(int(container.getScale() * image->width() * emoteScale),
|
||||
int(container.getScale() * image->height() * emoteScale));
|
||||
|
||||
container.addElement((new ImageLayoutElement(*this, image, size))
|
||||
container.addElement(this->makeImageLayoutElement(image, size)
|
||||
->setLink(this->getLink()));
|
||||
}
|
||||
else
|
||||
@@ -159,6 +159,27 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
|
||||
}
|
||||
}
|
||||
|
||||
MessageLayoutElement *EmoteElement::makeImageLayoutElement(
|
||||
const ImagePtr &image, const QSize &size)
|
||||
{
|
||||
return new ImageLayoutElement(*this, image, size);
|
||||
}
|
||||
|
||||
// MOD BADGE
|
||||
ModBadgeElement::ModBadgeElement(const EmotePtr &data,
|
||||
MessageElementFlags flags_)
|
||||
: EmoteElement(data, flags_)
|
||||
{
|
||||
}
|
||||
|
||||
MessageLayoutElement *ModBadgeElement::makeImageLayoutElement(
|
||||
const ImagePtr &image, const QSize &size)
|
||||
{
|
||||
static const QColor modBadgeBackgroundColor("#34AE0A");
|
||||
return new ImageWithBackgroundLayoutElement(*this, image, size,
|
||||
modBadgeBackgroundColor);
|
||||
}
|
||||
|
||||
// BADGE
|
||||
BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags)
|
||||
: MessageElement(flags)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
namespace chatterino {
|
||||
class Channel;
|
||||
struct MessageLayoutContainer;
|
||||
class MessageLayoutElement;
|
||||
|
||||
class Image;
|
||||
using ImagePtr = std::shared_ptr<Image>;
|
||||
@@ -209,11 +210,26 @@ public:
|
||||
MessageElementFlags flags_) override;
|
||||
EmotePtr getEmote() const;
|
||||
|
||||
protected:
|
||||
virtual MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
|
||||
const QSize &size);
|
||||
|
||||
private:
|
||||
std::unique_ptr<TextElement> textElement_;
|
||||
EmotePtr emote_;
|
||||
};
|
||||
|
||||
// Behaves like an emote element, except it creates a different image layout element that draws the mod badge background
|
||||
class ModBadgeElement : public EmoteElement
|
||||
{
|
||||
public:
|
||||
ModBadgeElement(const EmotePtr &data, MessageElementFlags flags_);
|
||||
|
||||
protected:
|
||||
MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
|
||||
const QSize &size) override;
|
||||
};
|
||||
|
||||
class BadgeElement : public MessageElement
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -168,6 +168,33 @@ int ImageLayoutElement::getXFromIndex(int index)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// IMAGE WITH BACKGROUND
|
||||
//
|
||||
ImageWithBackgroundLayoutElement::ImageWithBackgroundLayoutElement(
|
||||
MessageElement &creator, ImagePtr image, const QSize &size, QColor color)
|
||||
: ImageLayoutElement(creator, image, size)
|
||||
, color_(color)
|
||||
{
|
||||
}
|
||||
|
||||
void ImageWithBackgroundLayoutElement::paint(QPainter &painter)
|
||||
{
|
||||
if (this->image_ == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto pixmap = this->image_->pixmapOrLoad();
|
||||
if (pixmap && !this->image_->animated())
|
||||
{
|
||||
painter.fillRect(QRectF(this->getRect()), this->color_);
|
||||
|
||||
// fourtf: make it use qreal values
|
||||
painter.drawPixmap(QRectF(this->getRect()), *pixmap, QRectF());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// TEXT
|
||||
//
|
||||
|
||||
@@ -72,10 +72,22 @@ protected:
|
||||
int getMouseOverIndex(const QPoint &abs) const override;
|
||||
int getXFromIndex(int index) override;
|
||||
|
||||
private:
|
||||
ImagePtr image_;
|
||||
};
|
||||
|
||||
class ImageWithBackgroundLayoutElement : public ImageLayoutElement
|
||||
{
|
||||
public:
|
||||
ImageWithBackgroundLayoutElement(MessageElement &creator, ImagePtr image,
|
||||
const QSize &size, QColor color);
|
||||
|
||||
protected:
|
||||
void paint(QPainter &painter) override;
|
||||
|
||||
private:
|
||||
QColor color_;
|
||||
};
|
||||
|
||||
// TEXT
|
||||
class TextLayoutElement : public MessageLayoutElement
|
||||
{
|
||||
|
||||
@@ -21,13 +21,25 @@ namespace {
|
||||
return {urlTemplate.replace("{{id}}", id.string)
|
||||
.replace("{{image}}", emoteScale)};
|
||||
}
|
||||
|
||||
Url getEmoteLinkV3(const EmoteId &id, const QString &emoteScale)
|
||||
{
|
||||
static const QString urlTemplate(
|
||||
"https://cdn.betterttv.net/emote/%1/%2");
|
||||
|
||||
return {urlTemplate.arg(id.string, emoteScale)};
|
||||
}
|
||||
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
|
||||
{
|
||||
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
|
||||
static std::mutex mutex;
|
||||
|
||||
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
|
||||
}
|
||||
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
|
||||
const QJsonObject &jsonRoot, const EmoteMap ¤tEmotes)
|
||||
const QJsonArray &jsonEmotes, const EmoteMap ¤tEmotes)
|
||||
{
|
||||
auto emotes = EmoteMap();
|
||||
auto jsonEmotes = jsonRoot.value("emotes").toArray();
|
||||
auto urlTemplate =
|
||||
qS("https:") + jsonRoot.value("urlTemplate").toString();
|
||||
|
||||
for (auto jsonEmote : jsonEmotes)
|
||||
{
|
||||
@@ -37,10 +49,9 @@ namespace {
|
||||
|
||||
auto emote = Emote(
|
||||
{name,
|
||||
ImageSet{
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
|
||||
ImageSet{Image::fromUrl(getEmoteLinkV3(id, "1x"), 1),
|
||||
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5),
|
||||
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25)},
|
||||
Tooltip{name.string + "<br />Global BetterTTV Emote"},
|
||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
||||
|
||||
@@ -50,38 +61,36 @@ namespace {
|
||||
|
||||
return {Success, std::move(emotes)};
|
||||
}
|
||||
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
|
||||
{
|
||||
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
|
||||
static std::mutex mutex;
|
||||
|
||||
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
|
||||
}
|
||||
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
|
||||
{
|
||||
auto emotes = EmoteMap();
|
||||
auto jsonEmotes = jsonRoot.value("emotes").toArray();
|
||||
auto urlTemplate = "https:" + jsonRoot.value("urlTemplate").toString();
|
||||
|
||||
for (auto jsonEmote_ : jsonEmotes)
|
||||
{
|
||||
auto jsonEmote = jsonEmote_.toObject();
|
||||
auto innerParse = [&jsonRoot, &emotes](const char *key) {
|
||||
auto jsonEmotes = jsonRoot.value(key).toArray();
|
||||
for (auto jsonEmote_ : jsonEmotes)
|
||||
{
|
||||
auto jsonEmote = jsonEmote_.toObject();
|
||||
|
||||
auto id = EmoteId{jsonEmote.value("id").toString()};
|
||||
auto name = EmoteName{jsonEmote.value("code").toString()};
|
||||
// emoteObject.value("imageType").toString();
|
||||
auto id = EmoteId{jsonEmote.value("id").toString()};
|
||||
auto name = EmoteName{jsonEmote.value("code").toString()};
|
||||
// emoteObject.value("imageType").toString();
|
||||
|
||||
auto emote = Emote(
|
||||
{name,
|
||||
ImageSet{
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
|
||||
Tooltip{name.string + "<br />Channel BetterTTV Emote"},
|
||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
||||
auto emote = Emote(
|
||||
{name,
|
||||
ImageSet{
|
||||
Image::fromUrl(getEmoteLinkV3(id, "1x"), 1),
|
||||
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5),
|
||||
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25),
|
||||
},
|
||||
Tooltip{name.string + "<br />Channel BetterTTV Emote"},
|
||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
||||
|
||||
emotes[name] = cachedOrMake(std::move(emote), id);
|
||||
}
|
||||
emotes[name] = cachedOrMake(std::move(emote), id);
|
||||
}
|
||||
};
|
||||
|
||||
innerParse("channelEmotes");
|
||||
innerParse("sharedEmotes");
|
||||
|
||||
return {Success, std::move(emotes)};
|
||||
}
|
||||
@@ -116,7 +125,7 @@ void BttvEmotes::loadEmotes()
|
||||
.timeout(30000)
|
||||
.onSuccess([this](auto result) -> Outcome {
|
||||
auto emotes = this->global_.get();
|
||||
auto pair = parseGlobalEmotes(result.parseJson(), *emotes);
|
||||
auto pair = parseGlobalEmotes(result.parseJsonArray(), *emotes);
|
||||
if (pair.first)
|
||||
this->global_.set(
|
||||
std::make_shared<EmoteMap>(std::move(pair.second)));
|
||||
@@ -125,10 +134,10 @@ void BttvEmotes::loadEmotes()
|
||||
.execute();
|
||||
}
|
||||
|
||||
void BttvEmotes::loadChannel(const QString &channelName,
|
||||
void BttvEmotes::loadChannel(const QString &channelId,
|
||||
std::function<void(EmoteMap &&)> callback)
|
||||
{
|
||||
NetworkRequest(QString(bttvChannelEmoteApiUrl) + channelName)
|
||||
NetworkRequest(QString(bttvChannelEmoteApiUrl) + channelId)
|
||||
.timeout(3000)
|
||||
.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
|
||||
auto pair = parseChannelEmotes(result.parseJson());
|
||||
|
||||
@@ -14,9 +14,9 @@ class EmoteMap;
|
||||
class BttvEmotes final
|
||||
{
|
||||
static constexpr const char *globalEmoteApiUrl =
|
||||
"https://api.betterttv.net/2/emotes";
|
||||
"https://api.betterttv.net/3/cached/emotes/global";
|
||||
static constexpr const char *bttvChannelEmoteApiUrl =
|
||||
"https://api.betterttv.net/2/channels/";
|
||||
"https://api.betterttv.net/3/cached/users/twitch/";
|
||||
|
||||
public:
|
||||
BttvEmotes();
|
||||
@@ -24,7 +24,7 @@ public:
|
||||
std::shared_ptr<const EmoteMap> emotes() const;
|
||||
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
||||
void loadEmotes();
|
||||
static void loadChannel(const QString &channelName,
|
||||
static void loadChannel(const QString &channelId,
|
||||
std::function<void(EmoteMap &&)> callback);
|
||||
|
||||
private:
|
||||
|
||||
@@ -79,7 +79,38 @@ namespace {
|
||||
|
||||
return {Success, std::move(emotes)};
|
||||
}
|
||||
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
|
||||
|
||||
boost::optional<EmotePtr> parseModBadge(const QJsonObject &jsonRoot)
|
||||
{
|
||||
boost::optional<EmotePtr> modBadge;
|
||||
|
||||
auto room = jsonRoot.value("room").toObject();
|
||||
auto modUrls = room.value("mod_urls").toObject();
|
||||
if (!modUrls.isEmpty())
|
||||
{
|
||||
auto modBadge1x = getEmoteLink(modUrls, "1");
|
||||
auto modBadge2x = getEmoteLink(modUrls, "2");
|
||||
auto modBadge3x = getEmoteLink(modUrls, "4");
|
||||
|
||||
auto modBadgeImageSet = ImageSet{
|
||||
Image::fromUrl(modBadge1x, 1),
|
||||
modBadge2x.string.isEmpty() ? Image::getEmpty()
|
||||
: Image::fromUrl(modBadge2x, 0.5),
|
||||
modBadge3x.string.isEmpty() ? Image::getEmpty()
|
||||
: Image::fromUrl(modBadge3x, 0.25),
|
||||
};
|
||||
|
||||
modBadge = std::make_shared<Emote>(Emote{
|
||||
{""},
|
||||
modBadgeImageSet,
|
||||
Tooltip{"Twitch Channel Moderator"},
|
||||
modBadge1x,
|
||||
});
|
||||
}
|
||||
return modBadge;
|
||||
}
|
||||
|
||||
EmoteMap parseChannelEmotes(const QJsonObject &jsonRoot)
|
||||
{
|
||||
auto jsonSets = jsonRoot.value("sets").toObject();
|
||||
auto emotes = EmoteMap();
|
||||
@@ -110,7 +141,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
return {Success, std::move(emotes)};
|
||||
return emotes;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -138,7 +169,7 @@ void FfzEmotes::loadEmotes()
|
||||
QString url("https://api.frankerfacez.com/v1/set/global");
|
||||
|
||||
NetworkRequest(url)
|
||||
|
||||
|
||||
.timeout(30000)
|
||||
.onSuccess([this](auto result) -> Outcome {
|
||||
auto emotes = this->emotes();
|
||||
@@ -151,21 +182,28 @@ void FfzEmotes::loadEmotes()
|
||||
.execute();
|
||||
}
|
||||
|
||||
void FfzEmotes::loadChannel(const QString &channelName,
|
||||
std::function<void(EmoteMap &&)> callback)
|
||||
void FfzEmotes::loadChannel(
|
||||
const QString &channelId, std::function<void(EmoteMap &&)> emoteCallback,
|
||||
std::function<void(boost::optional<EmotePtr>)> modBadgeCallback)
|
||||
{
|
||||
log("[FFZEmotes] Reload FFZ Channel Emotes for channel {}\n", channelName);
|
||||
log("[FFZEmotes] Reload FFZ Channel Emotes for channel {}\n", channelId);
|
||||
|
||||
NetworkRequest("https://api.frankerfacez.com/v1/room/id/" + channelId)
|
||||
|
||||
NetworkRequest("https://api.frankerfacez.com/v1/room/" + channelName)
|
||||
|
||||
.timeout(20000)
|
||||
.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
|
||||
auto pair = parseChannelEmotes(result.parseJson());
|
||||
if (pair.first)
|
||||
callback(std::move(pair.second));
|
||||
return pair.first;
|
||||
.onSuccess([emoteCallback = std::move(emoteCallback),
|
||||
modBadgeCallback =
|
||||
std::move(modBadgeCallback)](auto result) -> Outcome {
|
||||
auto json = result.parseJson();
|
||||
auto emoteMap = parseChannelEmotes(json);
|
||||
auto modBadge = parseModBadge(json);
|
||||
|
||||
emoteCallback(std::move(emoteMap));
|
||||
modBadgeCallback(std::move(modBadge));
|
||||
|
||||
return Success;
|
||||
})
|
||||
.onError([channelName](int result) {
|
||||
.onError([channelId](int result) {
|
||||
if (result == 203)
|
||||
{
|
||||
// User does not have any FFZ emotes
|
||||
@@ -176,12 +214,12 @@ void FfzEmotes::loadChannel(const QString &channelName,
|
||||
{
|
||||
// TODO: Auto retry in case of a timeout, with a delay
|
||||
log("Fetching FFZ emotes for channel {} failed due to timeout",
|
||||
channelName);
|
||||
channelId);
|
||||
return true;
|
||||
}
|
||||
|
||||
log("Error fetching FFZ emotes for channel {}, error {}",
|
||||
channelName, result);
|
||||
log("Error fetching FFZ emotes for channel {}, error {}", channelId,
|
||||
result);
|
||||
|
||||
return true;
|
||||
})
|
||||
|
||||
@@ -22,8 +22,10 @@ public:
|
||||
std::shared_ptr<const EmoteMap> emotes() const;
|
||||
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
||||
void loadEmotes();
|
||||
static void loadChannel(const QString &channelName,
|
||||
std::function<void(EmoteMap &&)> callback);
|
||||
static void loadChannel(
|
||||
const QString &channelId,
|
||||
std::function<void(EmoteMap &&)> emoteCallback,
|
||||
std::function<void(boost::optional<EmotePtr>)> modBadgeCallback);
|
||||
|
||||
private:
|
||||
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#include "FfzModBadge.hpp"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QImageReader>
|
||||
#include <QJsonObject>
|
||||
#include <QPainter>
|
||||
#include <QString>
|
||||
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
FfzModBadge::FfzModBadge(const QString &channelName)
|
||||
: channelName_(channelName)
|
||||
{
|
||||
}
|
||||
|
||||
void FfzModBadge::loadCustomModBadge()
|
||||
{
|
||||
static QString partialUrl("https://cdn.frankerfacez.com/room-badge/mod/");
|
||||
|
||||
QString url = partialUrl + channelName_ + "/1";
|
||||
NetworkRequest(url)
|
||||
|
||||
.onSuccess([this, url](auto result) -> Outcome {
|
||||
auto data = result.getData();
|
||||
|
||||
QBuffer buffer(const_cast<QByteArray *>(&data));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
QImageReader reader(&buffer);
|
||||
if (reader.imageCount() == 0)
|
||||
return Failure;
|
||||
|
||||
QPixmap badgeOverlay = QPixmap::fromImageReader(&reader);
|
||||
QPixmap badgePixmap(18, 18);
|
||||
|
||||
// the default mod badge green color
|
||||
badgePixmap.fill(QColor("#34AE0A"));
|
||||
QPainter painter(&badgePixmap);
|
||||
QRectF rect(0, 0, 18, 18);
|
||||
painter.drawPixmap(rect, badgeOverlay, rect);
|
||||
|
||||
auto emote = Emote{{""},
|
||||
ImageSet{Image::fromPixmap(badgePixmap)},
|
||||
Tooltip{"Twitch Channel Moderator"},
|
||||
Url{url}};
|
||||
|
||||
this->badge_ = std::make_shared<Emote>(emote);
|
||||
// getBadge.execute();
|
||||
|
||||
return Success;
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
EmotePtr FfzModBadge::badge() const
|
||||
{
|
||||
return this->badge_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct Emote;
|
||||
using EmotePtr = std::shared_ptr<const Emote>;
|
||||
|
||||
class FfzModBadge
|
||||
{
|
||||
public:
|
||||
FfzModBadge(const QString &channelName);
|
||||
|
||||
void loadCustomModBadge();
|
||||
|
||||
EmotePtr badge() const;
|
||||
|
||||
private:
|
||||
const QString channelName_;
|
||||
EmotePtr badge_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -30,15 +30,18 @@ AbstractIrcServer::AbstractIrcServer()
|
||||
this->readConnection_.reset(new IrcConnection);
|
||||
this->readConnection_->moveToThread(QCoreApplication::instance()->thread());
|
||||
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::messageReceived,
|
||||
[this](auto msg) { this->messageReceived(msg); });
|
||||
QObject::connect(
|
||||
this->readConnection_.get(), &Communi::IrcConnection::messageReceived,
|
||||
[this](auto msg) { this->readConnectionMessageReceived(msg); });
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::privateMessageReceived,
|
||||
[this](auto msg) { this->privateMessageReceived(msg); });
|
||||
QObject::connect(
|
||||
this->readConnection_.get(), &Communi::IrcConnection::connected,
|
||||
[this] { this->onConnected(this->readConnection_.get()); });
|
||||
[this] { this->onReadConnected(this->readConnection_.get()); });
|
||||
QObject::connect(
|
||||
this->writeConnection_.get(), &Communi::IrcConnection::connected,
|
||||
[this] { this->onWriteConnected(this->writeConnection_.get()); });
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::disconnected,
|
||||
[this] { this->onDisconnected(); });
|
||||
@@ -227,7 +230,7 @@ std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty(
|
||||
return Channel::getEmpty();
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onConnected(IrcConnection *connection)
|
||||
void AbstractIrcServer::onReadConnected(IrcConnection *connection)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
@@ -262,6 +265,10 @@ void AbstractIrcServer::onConnected(IrcConnection *connection)
|
||||
this->falloffCounter_ = 1;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onWriteConnected(IrcConnection *connection)
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onDisconnected()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
@@ -310,7 +317,7 @@ void AbstractIrcServer::addFakeMessage(const QString &data)
|
||||
}
|
||||
else
|
||||
{
|
||||
this->messageReceived(fakeMessage);
|
||||
this->readConnectionMessageReceived(fakeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +326,8 @@ void AbstractIrcServer::privateMessageReceived(
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractIrcServer::messageReceived(Communi::IrcMessage *message)
|
||||
void AbstractIrcServer::readConnectionMessageReceived(
|
||||
Communi::IrcMessage *message)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -49,10 +49,11 @@ protected:
|
||||
const QString &channelName) = 0;
|
||||
|
||||
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
||||
virtual void messageReceived(Communi::IrcMessage *message);
|
||||
virtual void readConnectionMessageReceived(Communi::IrcMessage *message);
|
||||
virtual void writeConnectionMessageReceived(Communi::IrcMessage *message);
|
||||
|
||||
virtual void onConnected(IrcConnection *connection);
|
||||
virtual void onReadConnected(IrcConnection *connection);
|
||||
virtual void onWriteConnected(IrcConnection *connection);
|
||||
virtual void onDisconnected();
|
||||
virtual void onSocketError();
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ ChatroomChannel::ChatroomChannel(const QString &channelName,
|
||||
}
|
||||
}
|
||||
|
||||
void ChatroomChannel::refreshChannelEmotes()
|
||||
void ChatroomChannel::refreshBTTVChannelEmotes()
|
||||
{
|
||||
if (this->chatroomOwnerId.isEmpty())
|
||||
{
|
||||
@@ -36,17 +36,28 @@ void ChatroomChannel::refreshChannelEmotes()
|
||||
this->bttvEmotes_.set(
|
||||
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
||||
});
|
||||
FfzEmotes::loadChannel(username, [this, weak](auto &&emoteMap) {
|
||||
if (auto shared = weak.lock())
|
||||
this->ffzEmotes_.set(
|
||||
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
||||
});
|
||||
if (auto shared = weak.lock())
|
||||
{
|
||||
this->chatroomOwnerName = username;
|
||||
}
|
||||
});
|
||||
}
|
||||
void ChatroomChannel::refreshFFZChannelEmotes()
|
||||
{
|
||||
if (this->chatroomOwnerId.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
FfzEmotes::loadChannel(
|
||||
this->chatroomOwnerId,
|
||||
[this](auto &&emoteMap) {
|
||||
this->ffzEmotes_.set(
|
||||
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
||||
},
|
||||
[this](auto &&modBadge) {
|
||||
this->ffzCustomModBadge_.set(std::move(modBadge));
|
||||
});
|
||||
}
|
||||
|
||||
const QString &ChatroomChannel::getDisplayName() const
|
||||
{
|
||||
|
||||
@@ -13,7 +13,8 @@ protected:
|
||||
explicit ChatroomChannel(const QString &channelName,
|
||||
TwitchBadges &globalTwitchBadges,
|
||||
BttvEmotes &globalBttv, FfzEmotes &globalFfz);
|
||||
virtual void refreshChannelEmotes() override;
|
||||
virtual void refreshBTTVChannelEmotes() override;
|
||||
virtual void refreshFFZChannelEmotes() override;
|
||||
virtual const QString &getDisplayName() const override;
|
||||
|
||||
QString chatroomOwnerId;
|
||||
|
||||
@@ -582,49 +582,6 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
|
||||
}
|
||||
}
|
||||
|
||||
void IrcMessageHandler::handleWriteConnectionNoticeMessage(
|
||||
Communi::IrcNoticeMessage *message)
|
||||
{
|
||||
static std::unordered_set<std::string> readConnectionOnlyIDs{
|
||||
"host_on",
|
||||
"host_off",
|
||||
"host_target_went_offline",
|
||||
"emote_only_on",
|
||||
"emote_only_off",
|
||||
"slow_on",
|
||||
"slow_off",
|
||||
"subs_on",
|
||||
"subs_off",
|
||||
"r9k_on",
|
||||
"r9k_off",
|
||||
|
||||
// Display for user who times someone out. This implies you're a
|
||||
// moderator, at which point you will be connected to PubSub and receive
|
||||
// a better message from there
|
||||
"timeout_success",
|
||||
"ban_success",
|
||||
|
||||
// Channel suspended notices
|
||||
"msg_channel_suspended",
|
||||
};
|
||||
|
||||
QVariant v = message->tag("msg-id");
|
||||
if (v.isValid())
|
||||
{
|
||||
std::string msgID = v.toString().toStdString();
|
||||
|
||||
if (readConnectionOnlyIDs.find(msgID) != readConnectionOnlyIDs.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
log("Showing notice message from write connection with message id '{}'",
|
||||
msgID);
|
||||
}
|
||||
|
||||
this->handleNoticeMessage(message);
|
||||
}
|
||||
|
||||
void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
@@ -46,8 +46,6 @@ public:
|
||||
Communi::IrcNoticeMessage *message);
|
||||
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||
|
||||
void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||
|
||||
void handleJoinMessage(Communi::IrcMessage *message);
|
||||
void handlePartMessage(Communi::IrcMessage *message);
|
||||
|
||||
|
||||
@@ -699,13 +699,13 @@ PubSub::PubSub()
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["denied_automod_message"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
[](const auto &data, const auto &roomID) {
|
||||
// This message got denied by a moderator
|
||||
// qDebug() << QString::fromStdString(rj::stringify(data));
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["approved_automod_message"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
[](const auto &data, const auto &roomID) {
|
||||
// This message got approved by a moderator
|
||||
// qDebug() << QString::fromStdString(rj::stringify(data));
|
||||
};
|
||||
|
||||
@@ -87,7 +87,6 @@ TwitchChannel::TwitchChannel(const QString &name,
|
||||
, globalFfz_(ffz)
|
||||
, bttvEmotes_(std::make_shared<EmoteMap>())
|
||||
, ffzEmotes_(std::make_shared<EmoteMap>())
|
||||
, ffzCustomModBadge_(name)
|
||||
, mod_(false)
|
||||
{
|
||||
log("[TwitchChannel:{}] Opened", name);
|
||||
@@ -113,6 +112,8 @@ TwitchChannel::TwitchChannel(const QString &name,
|
||||
this->refreshLiveStatus();
|
||||
this->refreshBadges();
|
||||
this->refreshCheerEmotes();
|
||||
this->refreshFFZChannelEmotes();
|
||||
this->refreshBTTVChannelEmotes();
|
||||
});
|
||||
|
||||
// timers
|
||||
@@ -135,9 +136,7 @@ TwitchChannel::TwitchChannel(const QString &name,
|
||||
void TwitchChannel::initialize()
|
||||
{
|
||||
this->refreshChatters();
|
||||
this->refreshChannelEmotes();
|
||||
this->refreshBadges();
|
||||
this->ffzCustomModBadge_.loadCustomModBadge();
|
||||
}
|
||||
|
||||
bool TwitchChannel::isEmpty() const
|
||||
@@ -150,19 +149,30 @@ bool TwitchChannel::canSendMessage() const
|
||||
return !this->isEmpty();
|
||||
}
|
||||
|
||||
void TwitchChannel::refreshChannelEmotes()
|
||||
void TwitchChannel::refreshBTTVChannelEmotes()
|
||||
{
|
||||
BttvEmotes::loadChannel(
|
||||
this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
|
||||
this->roomId(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
|
||||
if (auto shared = weak.lock())
|
||||
this->bttvEmotes_.set(
|
||||
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
||||
});
|
||||
}
|
||||
|
||||
void TwitchChannel::refreshFFZChannelEmotes()
|
||||
{
|
||||
FfzEmotes::loadChannel(
|
||||
this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
|
||||
this->roomId(),
|
||||
[this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
|
||||
if (auto shared = weak.lock())
|
||||
this->ffzEmotes_.set(
|
||||
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
||||
},
|
||||
[this, weak = weakOf<Channel>(this)](auto &&modBadge) {
|
||||
if (auto shared = weak.lock())
|
||||
{
|
||||
this->ffzCustomModBadge_.set(std::move(modBadge));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -818,10 +828,7 @@ boost::optional<EmotePtr> TwitchChannel::twitchBadge(
|
||||
|
||||
boost::optional<EmotePtr> TwitchChannel::ffzCustomModBadge() const
|
||||
{
|
||||
if (auto badge = this->ffzCustomModBadge_.badge())
|
||||
return badge;
|
||||
|
||||
return boost::none;
|
||||
return this->ffzCustomModBadge_.get();
|
||||
}
|
||||
|
||||
boost::optional<CheerEmote> TwitchChannel::cheerEmote(const QString &string)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/UniqueAccess.hpp"
|
||||
#include "common/UsernameSet.hpp"
|
||||
#include "providers/ffz/FfzModBadge.hpp"
|
||||
#include "providers/twitch/TwitchEmotes.hpp"
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
@@ -85,7 +84,8 @@ public:
|
||||
std::shared_ptr<const EmoteMap> bttvEmotes() const;
|
||||
std::shared_ptr<const EmoteMap> ffzEmotes() const;
|
||||
|
||||
virtual void refreshChannelEmotes();
|
||||
virtual void refreshBTTVChannelEmotes();
|
||||
virtual void refreshFFZChannelEmotes();
|
||||
|
||||
// Badges
|
||||
boost::optional<EmotePtr> ffzCustomModBadge() const;
|
||||
@@ -150,13 +150,13 @@ protected:
|
||||
FfzEmotes &globalFfz_;
|
||||
Atomic<std::shared_ptr<const EmoteMap>> bttvEmotes_;
|
||||
Atomic<std::shared_ptr<const EmoteMap>> ffzEmotes_;
|
||||
Atomic<boost::optional<EmotePtr>> ffzCustomModBadge_;
|
||||
|
||||
private:
|
||||
// Badges
|
||||
UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>>
|
||||
badgeSets_; // "subscribers": { "0": ... "3": ... "6": ...
|
||||
UniqueAccess<std::vector<CheerEmoteSet>> cheerEmoteSets_;
|
||||
FfzModBadge ffzCustomModBadge_;
|
||||
|
||||
bool mod_ = false;
|
||||
bool vip_ = false;
|
||||
|
||||
@@ -952,7 +952,7 @@ void TwitchMessageBuilder::parseHighlights()
|
||||
{
|
||||
HighlightPhrase selfHighlight(
|
||||
currentUsername, getSettings()->enableSelfHighlightTaskbar,
|
||||
getSettings()->enableSelfHighlightSound, false);
|
||||
getSettings()->enableSelfHighlightSound, false, false);
|
||||
activeHighlights.emplace_back(std::move(selfHighlight));
|
||||
}
|
||||
|
||||
@@ -1171,7 +1171,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||
{
|
||||
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
|
||||
{
|
||||
this->emplace<BadgeElement>(
|
||||
this->emplace<ModBadgeElement>(
|
||||
customModBadge.get(),
|
||||
MessageElementFlag::BadgeChannelAuthority)
|
||||
->setTooltip((*customModBadge)->tooltip.string);
|
||||
|
||||
@@ -128,9 +128,8 @@ void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
|
||||
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
|
||||
}
|
||||
|
||||
void TwitchServer::messageReceived(Communi::IrcMessage *message)
|
||||
void TwitchServer::readConnectionMessageReceived(Communi::IrcMessage *message)
|
||||
{
|
||||
// this->readConnection
|
||||
if (message->type() == Communi::IrcMessage::Type::Private)
|
||||
{
|
||||
// We already have a handler for private messages
|
||||
@@ -141,39 +140,11 @@ void TwitchServer::messageReceived(Communi::IrcMessage *message)
|
||||
|
||||
auto &handler = IrcMessageHandler::getInstance();
|
||||
|
||||
if (command == "ROOMSTATE")
|
||||
{
|
||||
handler.handleRoomStateMessage(message);
|
||||
}
|
||||
else if (command == "CLEARCHAT")
|
||||
{
|
||||
handler.handleClearChatMessage(message);
|
||||
}
|
||||
else if (command == "CLEARMSG")
|
||||
{
|
||||
handler.handleClearMessageMessage(message);
|
||||
}
|
||||
else if (command == "USERSTATE")
|
||||
{
|
||||
handler.handleUserStateMessage(message);
|
||||
}
|
||||
else if (command == "WHISPER")
|
||||
{
|
||||
handler.handleWhisperMessage(message);
|
||||
}
|
||||
else if (command == "USERNOTICE")
|
||||
{
|
||||
handler.handleUserNoticeMessage(message, *this);
|
||||
}
|
||||
else if (command == "MODE")
|
||||
// Below commands enabled through the twitch.tv/membership CAP REQ
|
||||
if (command == "MODE")
|
||||
{
|
||||
handler.handleModeMessage(message);
|
||||
}
|
||||
else if (command == "NOTICE")
|
||||
{
|
||||
handler.handleNoticeMessage(
|
||||
static_cast<Communi::IrcNoticeMessage *>(message));
|
||||
}
|
||||
else if (command == "JOIN")
|
||||
{
|
||||
handler.handleJoinMessage(message);
|
||||
@@ -186,24 +157,58 @@ void TwitchServer::messageReceived(Communi::IrcMessage *message)
|
||||
|
||||
void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
|
||||
{
|
||||
switch (message->type())
|
||||
{
|
||||
case Communi::IrcMessage::Type::Notice:
|
||||
{
|
||||
IrcMessageHandler::getInstance().handleWriteConnectionNoticeMessage(
|
||||
static_cast<Communi::IrcNoticeMessage *>(message));
|
||||
}
|
||||
break;
|
||||
const QString &command = message->command();
|
||||
|
||||
default:;
|
||||
auto &handler = IrcMessageHandler::getInstance();
|
||||
|
||||
// Below commands enabled through the twitch.tv/commands CAP REQ
|
||||
if (command == "USERSTATE")
|
||||
{
|
||||
handler.handleUserStateMessage(message);
|
||||
}
|
||||
else if (command == "WHISPER")
|
||||
{
|
||||
handler.handleWhisperMessage(message);
|
||||
}
|
||||
else if (command == "USERNOTICE")
|
||||
{
|
||||
handler.handleUserNoticeMessage(message, *this);
|
||||
}
|
||||
else if (command == "ROOMSTATE")
|
||||
{
|
||||
handler.handleRoomStateMessage(message);
|
||||
}
|
||||
else if (command == "CLEARCHAT")
|
||||
{
|
||||
handler.handleClearChatMessage(message);
|
||||
}
|
||||
else if (command == "CLEARMSG")
|
||||
{
|
||||
handler.handleClearMessageMessage(message);
|
||||
}
|
||||
else if (command == "NOTICE")
|
||||
{
|
||||
handler.handleNoticeMessage(
|
||||
static_cast<Communi::IrcNoticeMessage *>(message));
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchServer::onConnected(IrcConnection *connection)
|
||||
void TwitchServer::onReadConnected(IrcConnection *connection)
|
||||
{
|
||||
// connection in thise case is the read connection
|
||||
connection->sendRaw(
|
||||
"CAP REQ :twitch.tv/tags twitch.tv/commands twitch.tv/membership");
|
||||
AbstractIrcServer::onReadConnected(connection);
|
||||
|
||||
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
|
||||
// twitch.tv/membership enables the JOIN/PART/MODE/NAMES commands. See https://dev.twitch.tv/docs/irc/membership/
|
||||
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/membership");
|
||||
}
|
||||
|
||||
void TwitchServer::onWriteConnected(IrcConnection *connection)
|
||||
{
|
||||
AbstractIrcServer::onWriteConnected(connection);
|
||||
|
||||
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
|
||||
// twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands/
|
||||
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands");
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> TwitchServer::getCustomChannel(
|
||||
|
||||
@@ -51,11 +51,13 @@ protected:
|
||||
|
||||
virtual void privateMessageReceived(
|
||||
Communi::IrcPrivateMessage *message) override;
|
||||
virtual void messageReceived(Communi::IrcMessage *message) override;
|
||||
virtual void readConnectionMessageReceived(
|
||||
Communi::IrcMessage *message) override;
|
||||
virtual void writeConnectionMessageReceived(
|
||||
Communi::IrcMessage *message) override;
|
||||
|
||||
virtual void onConnected(IrcConnection *connection) override;
|
||||
virtual void onReadConnected(IrcConnection *connection) override;
|
||||
virtual void onWriteConnected(IrcConnection *connection) override;
|
||||
|
||||
virtual std::shared_ptr<Channel> getCustomChannel(
|
||||
const QString &channelname) override;
|
||||
|
||||
@@ -198,11 +198,13 @@ public:
|
||||
QStringSetting streamlinkOpts = {"/external/streamlink/options", ""};
|
||||
|
||||
/// Misc
|
||||
BoolSetting betaUpdates = {"/misc/beta", false};
|
||||
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
||||
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
||||
BoolSetting loadTwitchMessageHistoryOnConnect = {
|
||||
"/misc/twitch/loadMessageHistoryOnConnect", true};
|
||||
IntSetting emotesTooltipPreview = {"/misc/emotesTooltipPreview", 0};
|
||||
BoolSetting openLinksIncognito = {"/misc/openLinksIncognito", 0};
|
||||
|
||||
QStringSetting cachePath = {"/cache/path", ""};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Updates.hpp"
|
||||
|
||||
#include "Settings.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/Version.hpp"
|
||||
@@ -13,6 +14,12 @@
|
||||
#include <QProcess>
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
QString currentBranch()
|
||||
{
|
||||
return getSettings()->betaUpdates ? "beta" : "stable";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Updates::Updates()
|
||||
: currentVersion_(CHATTERINO_VERSION)
|
||||
@@ -195,8 +202,8 @@ void Updates::installUpdates()
|
||||
void Updates::checkForUpdates()
|
||||
{
|
||||
QString url =
|
||||
"https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS
|
||||
"/stable";
|
||||
"https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/" +
|
||||
currentBranch();
|
||||
|
||||
NetworkRequest(url)
|
||||
.timeout(60000)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/Clamp.hpp"
|
||||
#include "widgets/AccountSwitchPopupWidget.hpp"
|
||||
#include "widgets/AccountSwitchPopup.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/Window.hpp"
|
||||
#include "widgets/dialogs/SettingsDialog.hpp"
|
||||
@@ -63,7 +63,7 @@ void WindowManager::showSettingsDialog(SettingsDialogPreference preference)
|
||||
void WindowManager::showAccountSelectPopup(QPoint point)
|
||||
{
|
||||
// static QWidget *lastFocusedWidget = nullptr;
|
||||
static AccountSwitchPopupWidget *w = new AccountSwitchPopupWidget();
|
||||
static AccountSwitchPopup *w = new AccountSwitchPopup();
|
||||
|
||||
if (w->hasFocus())
|
||||
{
|
||||
@@ -79,7 +79,7 @@ void WindowManager::showAccountSelectPopup(QPoint point)
|
||||
w->refresh();
|
||||
|
||||
QPoint buttonPos = point;
|
||||
w->move(buttonPos.x(), buttonPos.y());
|
||||
w->move(buttonPos.x() - 30, buttonPos.y());
|
||||
|
||||
w->show();
|
||||
w->setFocus();
|
||||
@@ -338,7 +338,7 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
|
||||
// Have to offset x by one because qt moves the window 1px too
|
||||
// far to the left:w
|
||||
|
||||
window.setGeometry(x + 1, y, width, height);
|
||||
window.setInitialBounds({x, y, width, height});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,10 +465,12 @@ void WindowManager::save()
|
||||
}
|
||||
|
||||
// window geometry
|
||||
window_obj.insert("x", window->x());
|
||||
window_obj.insert("y", window->y());
|
||||
window_obj.insert("width", window->width());
|
||||
window_obj.insert("height", window->height());
|
||||
auto rect = window->getBounds();
|
||||
|
||||
window_obj.insert("x", rect.x());
|
||||
window_obj.insert("y", rect.y());
|
||||
window_obj.insert("width", rect.width());
|
||||
window_obj.insert("height", rect.height());
|
||||
|
||||
// window tabs
|
||||
QJsonArray tabs_arr;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "widgets/AccountSwitchPopupWidget.hpp"
|
||||
#include "widgets/AccountSwitchPopup.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "widgets/dialogs/SettingsDialog.hpp"
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
AccountSwitchPopupWidget::AccountSwitchPopupWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
AccountSwitchPopup::AccountSwitchPopup(QWidget *parent)
|
||||
: BaseWindow(parent,
|
||||
BaseWindow::Flags(BaseWindow::TopMost | BaseWindow::Frameless))
|
||||
{
|
||||
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
|
||||
#ifdef Q_OS_LINUX
|
||||
this->setWindowFlag(Qt::Popup);
|
||||
#endif
|
||||
@@ -38,22 +38,22 @@ AccountSwitchPopupWidget::AccountSwitchPopupWidget(QWidget *parent)
|
||||
SettingsDialog::showDialog(SettingsDialogPreference::Accounts); //
|
||||
});
|
||||
|
||||
this->setLayout(vbox);
|
||||
this->getLayoutContainer()->setLayout(vbox);
|
||||
|
||||
// this->setStyleSheet("background: #333");
|
||||
this->setScaleIndependantSize(200, 200);
|
||||
}
|
||||
|
||||
void AccountSwitchPopupWidget::refresh()
|
||||
void AccountSwitchPopup::refresh()
|
||||
{
|
||||
this->ui_.accountSwitchWidget->refresh();
|
||||
}
|
||||
|
||||
void AccountSwitchPopupWidget::focusOutEvent(QFocusEvent *)
|
||||
void AccountSwitchPopup::focusOutEvent(QFocusEvent *)
|
||||
{
|
||||
this->hide();
|
||||
}
|
||||
|
||||
void AccountSwitchPopupWidget::paintEvent(QPaintEvent *)
|
||||
void AccountSwitchPopup::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/AccountSwitchWidget.hpp"
|
||||
#include "widgets/BaseWindow.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class AccountSwitchPopupWidget : public QWidget
|
||||
class AccountSwitchPopup : public BaseWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AccountSwitchPopupWidget(QWidget *parent = nullptr);
|
||||
AccountSwitchPopup(QWidget *parent = nullptr);
|
||||
|
||||
void refresh();
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/InitUpdateButton.hpp"
|
||||
#include "util/Shortcut.hpp"
|
||||
#include "widgets/AccountSwitchPopupWidget.hpp"
|
||||
#include "widgets/AccountSwitchPopup.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/dialogs/SettingsDialog.hpp"
|
||||
#include "widgets/dialogs/UpdateDialog.hpp"
|
||||
@@ -377,13 +377,13 @@ void Window::onAccountSelected()
|
||||
{
|
||||
auto user = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
#ifdef CHATTERINO_NIGHTLY_VERSION_STRING
|
||||
auto windowTitleEnd =
|
||||
QString("Chatterino Nightly " CHATTERINO_VERSION
|
||||
" (" UGLYMACROHACK(CHATTERINO_NIGHTLY_VERSION_STRING) ")");
|
||||
#else
|
||||
//#ifdef CHATTERINO_NIGHTLY_VERSION_STRING
|
||||
// auto windowTitleEnd =
|
||||
// QString("Chatterino Nightly " CHATTERINO_VERSION
|
||||
// " (" UGLYMACROHACK(CHATTERINO_NIGHTLY_VERSION_STRING) ")");
|
||||
//#else
|
||||
auto windowTitleEnd = QString("Chatterino " CHATTERINO_VERSION);
|
||||
#endif
|
||||
//#endif
|
||||
|
||||
this->setWindowTitle(windowTitleEnd);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
@@ -20,34 +21,30 @@ namespace chatterino {
|
||||
LogsPopup::LogsPopup()
|
||||
: channel_(Channel::getEmpty())
|
||||
{
|
||||
this->initLayout();
|
||||
this->resize(400, 600);
|
||||
}
|
||||
|
||||
void LogsPopup::initLayout()
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
|
||||
this->channelView_ = new ChannelView(this);
|
||||
layout->addWidget(this->channelView_);
|
||||
|
||||
this->setLayout(layout);
|
||||
}
|
||||
|
||||
void LogsPopup::setChannelName(QString channelName)
|
||||
{
|
||||
this->channelName_ = channelName;
|
||||
}
|
||||
|
||||
void LogsPopup::setChannel(std::shared_ptr<Channel> channel)
|
||||
void LogsPopup::setChannel(const ChannelPtr &channel)
|
||||
{
|
||||
this->channel_ = channel;
|
||||
this->updateWindowTitle();
|
||||
}
|
||||
|
||||
void LogsPopup::setTargetUserName(QString userName)
|
||||
void LogsPopup::setChannelName(const QString &channelName)
|
||||
{
|
||||
this->channelName_ = channelName;
|
||||
this->updateWindowTitle();
|
||||
}
|
||||
|
||||
void LogsPopup::setTargetUserName(const QString &userName)
|
||||
{
|
||||
this->userName_ = userName;
|
||||
this->updateWindowTitle();
|
||||
}
|
||||
|
||||
void LogsPopup::updateWindowTitle()
|
||||
{
|
||||
this->setWindowTitle(this->userName_ + "'s logs in #" + this->channelName_);
|
||||
}
|
||||
|
||||
void LogsPopup::getLogs()
|
||||
@@ -60,8 +57,6 @@ void LogsPopup::getLogs()
|
||||
this->channelName_ = twitchChannel->getName();
|
||||
this->getLogviewerLogs(twitchChannel->roomId());
|
||||
|
||||
this->setWindowTitle(this->userName_ + "'s logs in #" +
|
||||
this->channelName_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -83,7 +78,7 @@ void LogsPopup::setMessages(std::vector<MessagePtr> &messages)
|
||||
ChannelPtr logsChannel(new Channel("logs", Channel::Type::Misc));
|
||||
|
||||
logsChannel->addMessagesAtStart(messages);
|
||||
this->channelView_->setChannel(logsChannel);
|
||||
SearchPopup::setChannel(logsChannel);
|
||||
}
|
||||
|
||||
void LogsPopup::getLogviewerLogs(const QString &roomID)
|
||||
@@ -121,6 +116,8 @@ void LogsPopup::getLogviewerLogs(const QString &roomID)
|
||||
static_cast<Communi::IrcPrivateMessage *>(ircMessage);
|
||||
TwitchMessageBuilder builder(this->channel_.get(), privMsg,
|
||||
args);
|
||||
builder.message().searchText = message;
|
||||
|
||||
messages.push_back(builder.build());
|
||||
}
|
||||
|
||||
@@ -165,6 +162,7 @@ void LogsPopup::getOverrustleLogs()
|
||||
for (auto i : dataMessages)
|
||||
{
|
||||
QJsonObject singleMessage = i.toObject();
|
||||
auto text = singleMessage.value("text").toString();
|
||||
QTime timeStamp =
|
||||
QDateTime::fromSecsSinceEpoch(
|
||||
singleMessage.value("timestamp").toInt())
|
||||
@@ -175,9 +173,9 @@ void LogsPopup::getOverrustleLogs()
|
||||
builder.emplace<TextElement>(this->userName_,
|
||||
MessageElementFlag::Username,
|
||||
MessageColor::System);
|
||||
builder.emplace<TextElement>(
|
||||
singleMessage.value("text").toString(),
|
||||
MessageElementFlag::Text, MessageColor::Text);
|
||||
builder.emplace<TextElement>(text, MessageElementFlag::Text,
|
||||
MessageColor::Text);
|
||||
builder.message().searchText = text;
|
||||
messages.push_back(builder.release());
|
||||
}
|
||||
}
|
||||
@@ -191,4 +189,5 @@ void LogsPopup::getOverrustleLogs()
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,37 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/BaseWindow.hpp"
|
||||
#include "widgets/helper/SearchPopup.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
class ChannelView;
|
||||
|
||||
class Channel;
|
||||
using ChannelPtr = std::shared_ptr<Channel>;
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
class LogsPopup : public BaseWindow
|
||||
class LogsPopup : public SearchPopup
|
||||
{
|
||||
public:
|
||||
LogsPopup();
|
||||
|
||||
void setChannelName(QString channelName);
|
||||
void setChannel(std::shared_ptr<Channel> channel);
|
||||
void setTargetUserName(QString userName);
|
||||
void setChannel(const ChannelPtr &channel) override;
|
||||
void setChannelName(const QString &channelName);
|
||||
void setTargetUserName(const QString &userName);
|
||||
|
||||
void getLogs();
|
||||
|
||||
protected:
|
||||
void updateWindowTitle() override;
|
||||
|
||||
private:
|
||||
ChannelView *channelView_ = nullptr;
|
||||
ChannelPtr channel_;
|
||||
|
||||
QString userName_;
|
||||
QString channelName_;
|
||||
|
||||
void initLayout();
|
||||
void setMessages(std::vector<MessagePtr> &messages);
|
||||
void getOverrustleLogs();
|
||||
void getLogviewerLogs(const QString &roomID);
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
#include "widgets/dialogs/SettingsDialog.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/helper/Button.hpp"
|
||||
#include "widgets/helper/SettingsDialogTab.hpp"
|
||||
#include "widgets/settingspages/AboutPage.hpp"
|
||||
#include "widgets/settingspages/AccountsPage.hpp"
|
||||
#include "widgets/settingspages/AdvancedPage.hpp"
|
||||
#include "widgets/settingspages/BrowserExtensionPage.hpp"
|
||||
#include "widgets/settingspages/CommandPage.hpp"
|
||||
#include "widgets/settingspages/EmotesPage.hpp"
|
||||
#include "widgets/settingspages/ExternalToolsPage.hpp"
|
||||
#include "widgets/settingspages/FeelPage.hpp"
|
||||
#include "widgets/settingspages/GeneralPage.hpp"
|
||||
#include "widgets/settingspages/HighlightingPage.hpp"
|
||||
#include "widgets/settingspages/IgnoresPage.hpp"
|
||||
#include "widgets/settingspages/KeyboardSettingsPage.hpp"
|
||||
#include "widgets/settingspages/LogsPage.hpp"
|
||||
#include "widgets/settingspages/LookPage.hpp"
|
||||
#include "widgets/settingspages/ModerationPage.hpp"
|
||||
#include "widgets/settingspages/NotificationPage.hpp"
|
||||
#include "widgets/settingspages/SpecialChannelsPage.hpp"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLineEdit>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
@@ -30,6 +26,8 @@ SettingsDialog *SettingsDialog::handle = nullptr;
|
||||
SettingsDialog::SettingsDialog()
|
||||
: BaseWindow(nullptr, BaseWindow::DisableCustomScaling)
|
||||
{
|
||||
this->setWindowTitle("Chatterino Settings");
|
||||
|
||||
this->initUi();
|
||||
this->addTabs();
|
||||
|
||||
@@ -43,39 +41,55 @@ SettingsDialog::SettingsDialog()
|
||||
|
||||
void SettingsDialog::initUi()
|
||||
{
|
||||
LayoutCreator<SettingsDialog> layoutCreator(this);
|
||||
auto outerBox = LayoutCreator<SettingsDialog>(this)
|
||||
.setLayoutType<QVBoxLayout>()
|
||||
.withoutSpacing();
|
||||
|
||||
// tab pages
|
||||
layoutCreator.setLayoutType<QHBoxLayout>()
|
||||
.withoutSpacing()
|
||||
.emplace<QWidget>()
|
||||
// TOP
|
||||
auto title = outerBox.emplace<PageHeader>();
|
||||
auto edit = LayoutCreator<PageHeader>(title.getElement())
|
||||
.setLayoutType<QHBoxLayout>()
|
||||
.withoutMargin()
|
||||
.emplace<QLineEdit>()
|
||||
.assign(&this->ui_.search);
|
||||
edit->setPlaceholderText("Find in settings...");
|
||||
|
||||
QObject::connect(edit.getElement(), &QLineEdit::textChanged, this,
|
||||
&SettingsDialog::filterElements);
|
||||
|
||||
// CENTER
|
||||
auto centerBox =
|
||||
outerBox.emplace<QHBoxLayout>().withoutMargin().withoutSpacing();
|
||||
|
||||
// left side (tabs)
|
||||
centerBox.emplace<QWidget>()
|
||||
.assign(&this->ui_.tabContainerContainer)
|
||||
.emplace<QVBoxLayout>()
|
||||
.setLayoutType<QVBoxLayout>()
|
||||
.withoutMargin()
|
||||
.assign(&this->ui_.tabContainer);
|
||||
|
||||
this->ui_.tabContainerContainer->layout()->setContentsMargins(8, 8, 0, 39);
|
||||
|
||||
this->layout()->setSpacing(0);
|
||||
|
||||
// right side layout
|
||||
auto right = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
|
||||
// right side (pages)
|
||||
auto right =
|
||||
centerBox.emplace<QVBoxLayout>().withoutMargin().withoutSpacing();
|
||||
{
|
||||
right.emplace<QStackedLayout>()
|
||||
.assign(&this->ui_.pageStack)
|
||||
.withoutMargin();
|
||||
|
||||
auto buttons = right.emplace<QDialogButtonBox>(Qt::Horizontal);
|
||||
{
|
||||
this->ui_.okButton =
|
||||
buttons->addButton("Ok", QDialogButtonBox::YesRole);
|
||||
this->ui_.cancelButton =
|
||||
buttons->addButton("Cancel", QDialogButtonBox::NoRole);
|
||||
}
|
||||
}
|
||||
|
||||
this->ui_.pageStack->setMargin(0);
|
||||
|
||||
outerBox->addSpacing(12);
|
||||
|
||||
// BOTTOM
|
||||
auto buttons = outerBox.emplace<QDialogButtonBox>(Qt::Horizontal);
|
||||
{
|
||||
this->ui_.okButton =
|
||||
buttons->addButton("Ok", QDialogButtonBox::YesRole);
|
||||
this->ui_.cancelButton =
|
||||
buttons->addButton("Cancel", QDialogButtonBox::NoRole);
|
||||
}
|
||||
|
||||
// ---- misc
|
||||
this->ui_.tabContainerContainer->setObjectName("tabWidget");
|
||||
this->ui_.pageStack->setObjectName("pages");
|
||||
@@ -86,6 +100,50 @@ void SettingsDialog::initUi()
|
||||
&SettingsDialog::onCancelClicked);
|
||||
}
|
||||
|
||||
void SettingsDialog::filterElements(const QString &text)
|
||||
{
|
||||
// filter elements and hide pages
|
||||
for (auto &&page : this->pages_)
|
||||
{
|
||||
// filterElements returns true if anything on the page matches the search query
|
||||
page->tab()->setVisible(page->filterElements(text));
|
||||
}
|
||||
|
||||
// find next visible page
|
||||
if (this->lastSelectedByUser_ && this->lastSelectedByUser_->isVisible())
|
||||
{
|
||||
this->selectTab(this->lastSelectedByUser_, false);
|
||||
}
|
||||
else if (!this->selectedTab_->isVisible())
|
||||
{
|
||||
for (auto &&tab : this->tabs_)
|
||||
{
|
||||
if (tab->isVisible())
|
||||
{
|
||||
this->selectTab(tab, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove duplicate spaces
|
||||
bool shouldShowSpace = false;
|
||||
|
||||
for (int i = 0; i < this->ui_.tabContainer->count(); i++)
|
||||
{
|
||||
auto item = this->ui_.tabContainer->itemAt(i);
|
||||
if (auto x = dynamic_cast<QSpacerItem *>(item); x)
|
||||
{
|
||||
x->changeSize(10, shouldShowSpace ? int(16 * this->scale()) : 0);
|
||||
shouldShowSpace = false;
|
||||
}
|
||||
else if (item->widget())
|
||||
{
|
||||
shouldShowSpace |= item->widget()->isVisible();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDialog *SettingsDialog::getHandle()
|
||||
{
|
||||
return SettingsDialog::handle;
|
||||
@@ -96,7 +154,7 @@ void SettingsDialog::addTabs()
|
||||
this->ui_.tabContainer->setMargin(0);
|
||||
this->ui_.tabContainer->setSpacing(0);
|
||||
|
||||
this->ui_.tabContainer->addSpacing(16);
|
||||
this->ui_.tabContainer->setContentsMargins(0, 20, 0, 20);
|
||||
|
||||
this->addTab(new GeneralPage);
|
||||
|
||||
@@ -104,32 +162,21 @@ void SettingsDialog::addTabs()
|
||||
|
||||
this->addTab(new AccountsPage);
|
||||
|
||||
// this->ui_.tabContainer->addSpacing(16);
|
||||
|
||||
// this->addTab(new LookPage);
|
||||
// this->addTab(new FeelPage);
|
||||
|
||||
this->ui_.tabContainer->addSpacing(16);
|
||||
|
||||
this->addTab(new CommandPage);
|
||||
// this->addTab(new EmotesPage);
|
||||
this->addTab(new HighlightingPage);
|
||||
this->addTab(new IgnoresPage);
|
||||
|
||||
this->ui_.tabContainer->addSpacing(16);
|
||||
|
||||
this->addTab(new KeyboardSettingsPage);
|
||||
// this->addTab(new LogsPage);
|
||||
this->addTab(this->ui_.moderationPage = new ModerationPage);
|
||||
this->addTab(new NotificationPage);
|
||||
// this->addTab(new SpecialChannelsPage);
|
||||
// this->addTab(new BrowserExtensionPage);
|
||||
this->addTab(new ExternalToolsPage);
|
||||
this->addTab(new AdvancedPage);
|
||||
|
||||
this->ui_.tabContainer->addStretch(1);
|
||||
this->addTab(new AboutPage, Qt::AlignBottom);
|
||||
this->ui_.tabContainer->addSpacing(16);
|
||||
}
|
||||
|
||||
void SettingsDialog::addTab(SettingsPage *page, Qt::Alignment alignment)
|
||||
@@ -140,6 +187,7 @@ void SettingsDialog::addTab(SettingsPage *page, Qt::Alignment alignment)
|
||||
this->ui_.pageStack->addWidget(page);
|
||||
this->ui_.tabContainer->addWidget(tab, 0, alignment);
|
||||
this->tabs_.push_back(tab);
|
||||
this->pages_.push_back(page);
|
||||
|
||||
if (this->tabs_.size() == 1)
|
||||
{
|
||||
@@ -147,7 +195,7 @@ void SettingsDialog::addTab(SettingsPage *page, Qt::Alignment alignment)
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::selectTab(SettingsDialogTab *tab)
|
||||
void SettingsDialog::selectTab(SettingsDialogTab *tab, bool byUser)
|
||||
{
|
||||
this->ui_.pageStack->setCurrentWidget(tab->getSettingsPage());
|
||||
|
||||
@@ -159,10 +207,12 @@ void SettingsDialog::selectTab(SettingsDialogTab *tab)
|
||||
|
||||
tab->setSelected(true);
|
||||
tab->setStyleSheet("background: #222; color: #4FC3F7;"
|
||||
"border-left: 1px solid #444;"
|
||||
"border-top: 1px solid #444;"
|
||||
"border-bottom: 1px solid #444;");
|
||||
"/*border: 1px solid #555; border-right: none;*/");
|
||||
this->selectedTab_ = tab;
|
||||
if (byUser)
|
||||
{
|
||||
this->lastSelectedByUser_ = tab;
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::selectPage(SettingsPage *page)
|
||||
|
||||
@@ -8,12 +8,19 @@
|
||||
#include <QWidget>
|
||||
#include <pajlada/settings/setting.hpp>
|
||||
|
||||
class QLineEdit;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class SettingsPage;
|
||||
class SettingsDialogTab;
|
||||
class ModerationPage;
|
||||
|
||||
class PageHeader : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
};
|
||||
|
||||
enum class SettingsDialogPreference {
|
||||
NoPreference,
|
||||
Accounts,
|
||||
@@ -41,8 +48,9 @@ private:
|
||||
void initUi();
|
||||
void addTabs();
|
||||
void addTab(SettingsPage *page, Qt::Alignment alignment = Qt::AlignTop);
|
||||
void selectTab(SettingsDialogTab *tab);
|
||||
void selectTab(SettingsDialogTab *tab, bool byUser = true);
|
||||
void selectPage(SettingsPage *page);
|
||||
void filterElements(const QString &query);
|
||||
|
||||
void onOkClicked();
|
||||
void onCancelClicked();
|
||||
@@ -54,9 +62,12 @@ private:
|
||||
QPushButton *okButton{};
|
||||
QPushButton *cancelButton{};
|
||||
ModerationPage *moderationPage{};
|
||||
QLineEdit *search{};
|
||||
} ui_;
|
||||
std::vector<SettingsDialogTab *> tabs_;
|
||||
std::vector<SettingsPage *> pages_;
|
||||
SettingsDialogTab *selectedTab_{};
|
||||
SettingsDialogTab *lastSelectedByUser_{};
|
||||
|
||||
friend class SettingsDialogTab;
|
||||
};
|
||||
|
||||
@@ -24,8 +24,23 @@
|
||||
#define TEXT_FOLLOWERS "Followers: "
|
||||
#define TEXT_VIEWS "Views: "
|
||||
#define TEXT_CREATED "Created: "
|
||||
#define TEXT_USER_ID "ID: "
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
void addCopyableLabel(LayoutCreator<QHBoxLayout> box, Label **assign)
|
||||
{
|
||||
auto label = box.emplace<Label>().assign(assign);
|
||||
auto button = box.emplace<Button>();
|
||||
button->setPixmap(getApp()->resources->buttons.copyDark);
|
||||
button->setScaleIndependantSize(18, 18);
|
||||
button->setDim(Button::Dim::Lots);
|
||||
QObject::connect(button.getElement(), &Button::leftClicked,
|
||||
[label = label.getElement()] {
|
||||
qApp->clipboard()->setText(label->getText());
|
||||
});
|
||||
};
|
||||
} // namespace
|
||||
|
||||
UserInfoPopup::UserInfoPopup()
|
||||
: BaseWindow(nullptr, BaseWindow::Flags(BaseWindow::Frameless |
|
||||
@@ -50,6 +65,7 @@ UserInfoPopup::UserInfoPopup()
|
||||
auto avatar =
|
||||
head.emplace<Button>(nullptr).assign(&this->ui_.avatarButton);
|
||||
avatar->setScaleIndependantSize(100, 100);
|
||||
avatar->setDim(Button::Dim::None);
|
||||
QObject::connect(avatar.getElement(), &Button::leftClicked, [this] {
|
||||
QDesktopServices::openUrl(
|
||||
QUrl("https://twitch.tv/" + this->userName_.toLower()));
|
||||
@@ -58,11 +74,19 @@ UserInfoPopup::UserInfoPopup()
|
||||
// items on the right
|
||||
auto vbox = head.emplace<QVBoxLayout>();
|
||||
{
|
||||
auto name = vbox.emplace<Label>().assign(&this->ui_.nameLabel);
|
||||
{
|
||||
auto box = vbox.emplace<QHBoxLayout>()
|
||||
.withoutMargin()
|
||||
.withoutSpacing();
|
||||
addCopyableLabel(box, &this->ui_.nameLabel);
|
||||
this->ui_.nameLabel->setFontStyle(FontStyle::UiMediumBold);
|
||||
box->addStretch(1);
|
||||
addCopyableLabel(box, &this->ui_.userIDLabel);
|
||||
auto palette = QPalette();
|
||||
palette.setColor(QPalette::WindowText, QColor("#aaa"));
|
||||
this->ui_.userIDLabel->setPalette(palette);
|
||||
}
|
||||
|
||||
auto font = name->font();
|
||||
font.setBold(true);
|
||||
name->setFont(font);
|
||||
vbox.emplace<Label>(TEXT_VIEWS).assign(&this->ui_.viewCountLabel);
|
||||
vbox.emplace<Label>(TEXT_FOLLOWERS)
|
||||
.assign(&this->ui_.followerCountLabel);
|
||||
@@ -199,7 +223,7 @@ UserInfoPopup::UserInfoPopup()
|
||||
});
|
||||
}
|
||||
|
||||
this->setStyleSheet("font-size: 11pt;");
|
||||
// this->setStyleSheet("font-size: 11pt;");
|
||||
|
||||
this->installEvents();
|
||||
}
|
||||
@@ -208,7 +232,17 @@ void UserInfoPopup::themeChangedEvent()
|
||||
{
|
||||
BaseWindow::themeChangedEvent();
|
||||
|
||||
this->setStyleSheet("background: #333");
|
||||
this->setStyleSheet(
|
||||
"background: #333; font-size: " +
|
||||
QString::number(getFonts()
|
||||
->getFont(FontStyle::UiMediumBold, this->scale())
|
||||
.pixelSize()) +
|
||||
"px;");
|
||||
}
|
||||
|
||||
void UserInfoPopup::scaleChangedEvent(float /*scale*/)
|
||||
{
|
||||
themeChangedEvent();
|
||||
}
|
||||
|
||||
void UserInfoPopup::installEvents()
|
||||
@@ -348,6 +382,10 @@ void UserInfoPopup::updateUserData()
|
||||
|
||||
this->userId_ = id;
|
||||
|
||||
this->ui_.userIDLabel->setText(TEXT_USER_ID + this->userId_);
|
||||
// don't wait for the request to complete, just put the user id in the card
|
||||
// right away
|
||||
|
||||
QString url("https://api.twitch.tv/kraken/channels/" + id);
|
||||
|
||||
NetworkRequest::twitchRequest(url)
|
||||
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
|
||||
protected:
|
||||
virtual void themeChangedEvent() override;
|
||||
virtual void scaleChangedEvent(float scale) override;
|
||||
|
||||
private:
|
||||
void installEvents();
|
||||
@@ -48,6 +49,7 @@ private:
|
||||
Label *viewCountLabel = nullptr;
|
||||
Label *followerCountLabel = nullptr;
|
||||
Label *createdDateLabel = nullptr;
|
||||
Label *userIDLabel = nullptr;
|
||||
|
||||
QCheckBox *follow = nullptr;
|
||||
QCheckBox *ignore = nullptr;
|
||||
|
||||
@@ -133,6 +133,8 @@ ChannelView::ChannelView(BaseWidget *parent)
|
||||
this->clickTimer_ = new QTimer(this);
|
||||
this->clickTimer_->setSingleShot(true);
|
||||
this->clickTimer_->setInterval(500);
|
||||
|
||||
this->setFocusPolicy(Qt::FocusPolicy::StrongFocus);
|
||||
}
|
||||
|
||||
void ChannelView::initializeLayout()
|
||||
@@ -1526,14 +1528,8 @@ void ChannelView::addContextMenuItems(
|
||||
QString url = hoveredElement->getLink().value;
|
||||
|
||||
// open link
|
||||
bool incognitoByDefault = supportsIncognitoLinks() &&
|
||||
layout->getMessage()->loginName == "hemirt";
|
||||
menu->addAction("Open link", [url, incognitoByDefault] {
|
||||
if (incognitoByDefault)
|
||||
openLinkIncognito(url);
|
||||
else
|
||||
QDesktopServices::openUrl(QUrl(url));
|
||||
});
|
||||
menu->addAction("Open link",
|
||||
[url] { QDesktopServices::openUrl(QUrl(url)); });
|
||||
// open link default
|
||||
if (supportsIncognitoLinks())
|
||||
{
|
||||
@@ -1699,7 +1695,10 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
|
||||
|
||||
case Link::Url:
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl(link.value));
|
||||
if (getSettings()->openLinksIncognito && supportsIncognitoLinks())
|
||||
openLinkIncognito(link.value);
|
||||
else
|
||||
QDesktopServices::openUrl(QUrl(link.value));
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -10,6 +10,26 @@
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
ChannelPtr filter(const QString &text, const QString &channelName,
|
||||
const LimitedQueueSnapshot<MessagePtr> &snapshot)
|
||||
{
|
||||
ChannelPtr channel(new Channel(channelName, Channel::Type::None));
|
||||
|
||||
for (size_t i = 0; i < snapshot.size(); i++)
|
||||
{
|
||||
MessagePtr message = snapshot[i];
|
||||
|
||||
if (text.isEmpty() ||
|
||||
message->searchText.indexOf(text, 0, Qt::CaseInsensitive) != -1)
|
||||
{
|
||||
channel->addMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
return channel;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SearchPopup::SearchPopup()
|
||||
{
|
||||
@@ -17,13 +37,24 @@ SearchPopup::SearchPopup()
|
||||
this->resize(400, 600);
|
||||
}
|
||||
|
||||
void SearchPopup::setChannel(ChannelPtr channel)
|
||||
void SearchPopup::setChannel(const ChannelPtr &channel)
|
||||
{
|
||||
this->channelName_ = channel->getName();
|
||||
this->snapshot_ = channel->getMessageSnapshot();
|
||||
this->performSearch();
|
||||
this->search();
|
||||
|
||||
this->setWindowTitle("Searching in " + channel->getName() + "s history");
|
||||
this->updateWindowTitle();
|
||||
}
|
||||
|
||||
void SearchPopup::updateWindowTitle()
|
||||
{
|
||||
this->setWindowTitle("Searching in " + this->channelName_ + "s history");
|
||||
}
|
||||
|
||||
void SearchPopup::search()
|
||||
{
|
||||
this->channelView_->setChannel(filter(this->searchInput_->text(),
|
||||
this->channelName_, this->snapshot_));
|
||||
}
|
||||
|
||||
void SearchPopup::keyPressEvent(QKeyEvent *e)
|
||||
@@ -43,18 +74,20 @@ void SearchPopup::initLayout()
|
||||
{
|
||||
QVBoxLayout *layout1 = new QVBoxLayout(this);
|
||||
layout1->setMargin(0);
|
||||
layout1->setSpacing(0);
|
||||
|
||||
// HBOX
|
||||
{
|
||||
QHBoxLayout *layout2 = new QHBoxLayout(this);
|
||||
layout2->setMargin(6);
|
||||
layout2->setMargin(8);
|
||||
layout2->setSpacing(8);
|
||||
|
||||
// SEARCH INPUT
|
||||
{
|
||||
this->searchInput_ = new QLineEdit(this);
|
||||
layout2->addWidget(this->searchInput_);
|
||||
QObject::connect(this->searchInput_, &QLineEdit::returnPressed,
|
||||
[this] { this->performSearch(); });
|
||||
[this] { this->search(); });
|
||||
}
|
||||
|
||||
// SEARCH BUTTON
|
||||
@@ -63,7 +96,7 @@ void SearchPopup::initLayout()
|
||||
searchButton->setText("Search");
|
||||
layout2->addWidget(searchButton);
|
||||
QObject::connect(searchButton, &QPushButton::clicked,
|
||||
[this] { this->performSearch(); });
|
||||
[this] { this->search(); });
|
||||
}
|
||||
|
||||
layout1->addLayout(layout2);
|
||||
@@ -80,25 +113,4 @@ void SearchPopup::initLayout()
|
||||
}
|
||||
}
|
||||
|
||||
void SearchPopup::performSearch()
|
||||
{
|
||||
QString text = searchInput_->text();
|
||||
|
||||
ChannelPtr channel(new Channel(this->channelName_, Channel::Type::None));
|
||||
|
||||
for (size_t i = 0; i < this->snapshot_.size(); i++)
|
||||
{
|
||||
MessagePtr message = this->snapshot_[i];
|
||||
|
||||
if (text.isEmpty() ||
|
||||
message->searchText.indexOf(this->searchInput_->text(), 0,
|
||||
Qt::CaseInsensitive) != -1)
|
||||
{
|
||||
channel->addMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
this->channelView_->setChannel(channel);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "ForwardDecl.hpp"
|
||||
#include "messages/LimitedQueueSnapshot.hpp"
|
||||
#include "widgets/BaseWindow.hpp"
|
||||
|
||||
@@ -9,30 +10,26 @@ class QLineEdit;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
class ChannelView;
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
class SearchPopup : public BaseWindow
|
||||
{
|
||||
public:
|
||||
SearchPopup();
|
||||
|
||||
void setChannel(std::shared_ptr<Channel> channel);
|
||||
virtual void setChannel(const ChannelPtr &channel);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
|
||||
virtual void updateWindowTitle();
|
||||
|
||||
private:
|
||||
void initLayout();
|
||||
void performSearch();
|
||||
void search();
|
||||
|
||||
LimitedQueueSnapshot<MessagePtr> snapshot_;
|
||||
QLineEdit *searchInput_;
|
||||
ChannelView *channelView_;
|
||||
QString channelName_;
|
||||
QLineEdit *searchInput_{};
|
||||
ChannelView *channelView_{};
|
||||
QString channelName_{};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -68,6 +68,8 @@ void SettingsDialogTab::mousePressEvent(QMouseEvent *event)
|
||||
}
|
||||
|
||||
this->dialog_->selectTab(this);
|
||||
|
||||
this->setFocus();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
#include "AdvancedPage.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/taggedusers/TaggedUsersController.hpp"
|
||||
#include "controllers/taggedusers/TaggedUsersModel.hpp"
|
||||
#include "singletons/Logging.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QListView>
|
||||
#include <QPushButton>
|
||||
#include <QTableView>
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
AdvancedPage::AdvancedPage()
|
||||
: SettingsPage("Advanced", ":/settings/advanced.svg")
|
||||
{
|
||||
LayoutCreator<AdvancedPage> layoutCreator(this);
|
||||
|
||||
auto tabs = layoutCreator.emplace<QTabWidget>();
|
||||
|
||||
{
|
||||
auto layout = tabs.appendTab(new QVBoxLayout, "Cache");
|
||||
auto folderLabel = layout.emplace<QLabel>();
|
||||
|
||||
folderLabel->setTextFormat(Qt::RichText);
|
||||
folderLabel->setTextInteractionFlags(Qt::TextBrowserInteraction |
|
||||
Qt::LinksAccessibleByKeyboard |
|
||||
Qt::LinksAccessibleByKeyboard);
|
||||
folderLabel->setOpenExternalLinks(true);
|
||||
|
||||
getSettings()->cachePath.connect([folderLabel](const auto &,
|
||||
auto) mutable {
|
||||
QString newPath = getPaths()->cacheDirectory();
|
||||
|
||||
QString pathShortened = "Cache saved at <a href=\"file:///" +
|
||||
newPath +
|
||||
"\"><span style=\"color: white;\">" +
|
||||
shortenString(newPath, 50) + "</span></a>";
|
||||
|
||||
folderLabel->setText(pathShortened);
|
||||
folderLabel->setToolTip(newPath);
|
||||
});
|
||||
|
||||
layout->addStretch(1);
|
||||
|
||||
auto selectDir = layout.emplace<QPushButton>("Set custom cache folder");
|
||||
|
||||
QObject::connect(
|
||||
selectDir.getElement(), &QPushButton::clicked, this, [this] {
|
||||
auto dirName = QFileDialog::getExistingDirectory(this);
|
||||
|
||||
getSettings()->cachePath = dirName;
|
||||
});
|
||||
|
||||
auto resetDir =
|
||||
layout.emplace<QPushButton>("Reset custom cache folder");
|
||||
QObject::connect(resetDir.getElement(), &QPushButton::clicked, this,
|
||||
[]() mutable {
|
||||
getSettings()->cachePath = ""; //
|
||||
});
|
||||
|
||||
// Logs end
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/settingspages/SettingsPage.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class AdvancedPage : public SettingsPage
|
||||
{
|
||||
public:
|
||||
AdvancedPage();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,36 +0,0 @@
|
||||
#include "BrowserExtensionPage.hpp"
|
||||
|
||||
#include "util/LayoutCreator.hpp"
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
#define CHROME_EXTENSION_LINK \
|
||||
"https://chrome.google.com/webstore/detail/chatterino-native-host/" \
|
||||
"glknmaideaikkmemifbfkhnomoknepka"
|
||||
#define FIREFOX_EXTENSION_LINK \
|
||||
"https://addons.mozilla.org/en-US/firefox/addon/chatterino-native-host/"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
BrowserExtensionPage::BrowserExtensionPage()
|
||||
: SettingsPage("Browser integration", ":/settings/browser.svg")
|
||||
{
|
||||
auto layout =
|
||||
LayoutCreator<BrowserExtensionPage>(this).setLayoutType<QVBoxLayout>();
|
||||
|
||||
auto label = layout.emplace<QLabel>(
|
||||
"The browser extension will replace the default Twitch.tv chat with "
|
||||
"chatterino while chatterino is running.");
|
||||
label->setWordWrap(true);
|
||||
|
||||
auto chrome = layout.emplace<QLabel>("<a href=\"" CHROME_EXTENSION_LINK
|
||||
"\">Download for Google Chrome</a>");
|
||||
chrome->setOpenExternalLinks(true);
|
||||
auto firefox =
|
||||
layout.emplace<QLabel>("<a href=\"" FIREFOX_EXTENSION_LINK
|
||||
"\">Download for Mozilla Firefox</a>");
|
||||
firefox->setOpenExternalLinks(true);
|
||||
layout->addStretch(1);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/settingspages/SettingsPage.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class BrowserExtensionPage : public SettingsPage
|
||||
{
|
||||
public:
|
||||
BrowserExtensionPage();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -38,7 +38,7 @@ CommandPage::CommandPage()
|
||||
auto app = getApp();
|
||||
|
||||
LayoutCreator<CommandPage> layoutCreator(this);
|
||||
auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
|
||||
auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
|
||||
|
||||
EditableModelView *view =
|
||||
layout.emplace<EditableModelView>(app->commands->createModel(nullptr))
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#include "EmotesPage.hpp"
|
||||
|
||||
#include "util/LayoutCreator.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
EmotesPage::EmotesPage()
|
||||
: SettingsPage("Emotes", ":/settings/emote.svg")
|
||||
{
|
||||
// SettingManager &settings = SettingManager::getInstance();
|
||||
// LayoutCreator<EmotesPage> layoutCreator(this);
|
||||
// auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
|
||||
|
||||
// // clang-format off
|
||||
// layout.append(this->createCheckBox("Enable Twitch emotes",
|
||||
// settings.enableTwitchEmotes));
|
||||
// layout.append(this->createCheckBox("Enable BetterTTV emotes",
|
||||
// settings.enableBttvEmotes));
|
||||
// layout.append(this->createCheckBox("Enable FrankerFaceZ emotes",
|
||||
// settings.enableFfzEmotes)); layout.append(this->createCheckBox("Enable
|
||||
// emojis", settings.enableEmojis));
|
||||
// layout.append(this->createCheckBox("Enable gif animations",
|
||||
// settings.enableGifAnimations));
|
||||
// // clang-format on
|
||||
|
||||
// layout->addStretch(1);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/settingspages/SettingsPage.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class EmotesPage : public SettingsPage
|
||||
{
|
||||
public:
|
||||
EmotesPage();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,103 +0,0 @@
|
||||
#include "FeelPage.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#define PAUSE_HOVERING "When hovering"
|
||||
#define SCROLL_SMOOTH "Smooth scrolling"
|
||||
#define SCROLL_NEWMSG "Smooth scrolling for new messages"
|
||||
|
||||
#define LIMIT_CHATTERS_FOR_SMALLER_STREAMERS \
|
||||
"Only fetch chatters list for viewers under X viewers"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
FeelPage::FeelPage()
|
||||
: SettingsPage("Feel", ":/settings/behave.svg")
|
||||
{
|
||||
LayoutCreator<FeelPage> layoutCreator(this);
|
||||
|
||||
auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
|
||||
|
||||
// layout.append(this->createCheckBox("Use a seperate write connection.",
|
||||
// getSettings()->twitchSeperateWriteConnection));
|
||||
layout.append(this->createCheckBox(SCROLL_SMOOTH,
|
||||
getSettings()->enableSmoothScrolling));
|
||||
layout.append(this->createCheckBox(
|
||||
SCROLL_NEWMSG, getSettings()->enableSmoothScrollingNewMessages));
|
||||
|
||||
auto form = layout.emplace<QFormLayout>().withoutMargin();
|
||||
{
|
||||
form->addRow(
|
||||
"", this->createCheckBox(
|
||||
"Show which users joined the channel (up to 1000 chatters)",
|
||||
getSettings()->showJoins));
|
||||
form->addRow(
|
||||
"", this->createCheckBox(
|
||||
"Show which users parted the channel (up to 1000 chatters)",
|
||||
getSettings()->showParts));
|
||||
|
||||
form->addRow("Pause chat:",
|
||||
this->createCheckBox(PAUSE_HOVERING,
|
||||
getSettings()->pauseChatOnHover));
|
||||
|
||||
form->addRow("Mouse scroll speed:", this->createMouseScrollSlider());
|
||||
form->addRow("Links:",
|
||||
this->createCheckBox("Open links only on double click",
|
||||
getSettings()->linksDoubleClickOnly));
|
||||
form->addRow("", this->createCheckBox("Show link info in tooltips",
|
||||
getSettings()->linkInfoTooltip));
|
||||
form->addRow(
|
||||
"", this->createCheckBox("Auto unshort links (requires restart)",
|
||||
getSettings()->unshortLinks));
|
||||
}
|
||||
|
||||
layout->addSpacing(16);
|
||||
|
||||
{
|
||||
auto group = layout.emplace<QGroupBox>("Auto-completion");
|
||||
auto groupLayout = group.setLayoutType<QFormLayout>();
|
||||
groupLayout->addRow(
|
||||
LIMIT_CHATTERS_FOR_SMALLER_STREAMERS,
|
||||
this->createCheckBox(
|
||||
"", getSettings()->onlyFetchChattersForSmallerStreamers));
|
||||
|
||||
groupLayout->addRow(
|
||||
"What viewer count counts as a \"smaller streamer\"",
|
||||
this->createSpinBox(getSettings()->smallStreamerLimit, 10, 50000));
|
||||
}
|
||||
|
||||
{
|
||||
auto group = layout.emplace<QGroupBox>("Misc");
|
||||
auto groupLayout = group.setLayoutType<QVBoxLayout>();
|
||||
|
||||
groupLayout.append(this->createCheckBox("Show whispers inline",
|
||||
getSettings()->inlineWhispers));
|
||||
}
|
||||
|
||||
layout->addStretch(1);
|
||||
}
|
||||
|
||||
QSlider *FeelPage::createMouseScrollSlider()
|
||||
{
|
||||
auto slider = new QSlider(Qt::Horizontal);
|
||||
|
||||
float currentValue = getSettings()->mouseScrollMultiplier;
|
||||
int sliderValue = int(((currentValue - 0.1f) / 2.f) * 99.f);
|
||||
slider->setValue(sliderValue);
|
||||
|
||||
QObject::connect(slider, &QSlider::valueChanged, [=](int newValue) {
|
||||
float mul = static_cast<float>(newValue) / 99.f;
|
||||
float newSliderValue = (mul * 2.1f) + 0.1f;
|
||||
getSettings()->mouseScrollMultiplier = newSliderValue;
|
||||
});
|
||||
|
||||
return slider;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QSlider>
|
||||
|
||||
#include "widgets/settingspages/SettingsPage.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class FeelPage : public SettingsPage
|
||||
{
|
||||
public:
|
||||
FeelPage();
|
||||
|
||||
private:
|
||||
QSlider *createMouseScrollSlider();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/FuzzyConvert.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/IncognitoBrowser.hpp"
|
||||
#include "widgets/BaseWindow.hpp"
|
||||
#include "widgets/helper/Line.hpp"
|
||||
|
||||
@@ -20,36 +21,21 @@
|
||||
#define FIREFOX_EXTENSION_LINK \
|
||||
"https://addons.mozilla.org/en-US/firefox/addon/chatterino-native-host/"
|
||||
|
||||
#define addTitle addTitle
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
QPushButton *makeOpenSettingDirButton()
|
||||
{
|
||||
auto button = new QPushButton("Open settings directory");
|
||||
QObject::connect(button, &QPushButton::clicked, [] {
|
||||
QDesktopServices::openUrl(getPaths()->rootAppDataDirectory);
|
||||
});
|
||||
return button;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TitleLabel *SettingsLayout::addTitle(const QString &title)
|
||||
{
|
||||
auto label = new TitleLabel(title + ":");
|
||||
|
||||
if (this->count() != 0)
|
||||
this->addSpacing(16);
|
||||
|
||||
if (this->count() == 0)
|
||||
label->setStyleSheet("margin-top: 0");
|
||||
this->addWidget(label);
|
||||
return label;
|
||||
}
|
||||
|
||||
TitleLabel2 *SettingsLayout::addTitle2(const QString &title)
|
||||
{
|
||||
auto label = new TitleLabel2(title + ":");
|
||||
// groups
|
||||
this->groups_.push_back(Group{title, label, {}});
|
||||
|
||||
this->addSpacing(16);
|
||||
|
||||
this->addWidget(label);
|
||||
return label;
|
||||
}
|
||||
|
||||
@@ -71,6 +57,10 @@ QCheckBox *SettingsLayout::addCheckbox(const QString &text,
|
||||
[&setting, inverse](bool state) { setting = inverse ^ state; });
|
||||
|
||||
this->addWidget(check);
|
||||
|
||||
// groups
|
||||
this->groups_.back().widgets.push_back({check, {text}});
|
||||
|
||||
return check;
|
||||
}
|
||||
|
||||
@@ -82,11 +72,17 @@ ComboBox *SettingsLayout::addDropdown(const QString &text,
|
||||
combo->setFocusPolicy(Qt::StrongFocus);
|
||||
combo->addItems(list);
|
||||
|
||||
layout->addWidget(new QLabel(text + ":"));
|
||||
auto label = new QLabel(text + ":");
|
||||
layout->addWidget(label);
|
||||
layout->addStretch(1);
|
||||
layout->addWidget(combo);
|
||||
|
||||
this->addLayout(layout);
|
||||
|
||||
// groups
|
||||
this->groups_.back().widgets.push_back({combo, {text}});
|
||||
this->groups_.back().widgets.push_back({label, {text}});
|
||||
|
||||
return combo;
|
||||
}
|
||||
|
||||
@@ -124,6 +120,9 @@ DescriptionLabel *SettingsLayout::addDescription(const QString &text)
|
||||
|
||||
this->addWidget(label);
|
||||
|
||||
// groups
|
||||
this->groups_.back().widgets.push_back({label, {text}});
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
@@ -132,6 +131,64 @@ void SettingsLayout::addSeperator()
|
||||
this->addWidget(new Line(false));
|
||||
}
|
||||
|
||||
bool SettingsLayout::filterElements(const QString &query)
|
||||
{
|
||||
bool any{};
|
||||
|
||||
for (auto &&group : this->groups_)
|
||||
{
|
||||
// if a description in a group matches `query` then show the entire group
|
||||
bool descriptionMatches{};
|
||||
for (auto &&widget : group.widgets)
|
||||
{
|
||||
if (auto x = dynamic_cast<DescriptionLabel *>(widget.element); x)
|
||||
{
|
||||
if (x->text().contains(query, Qt::CaseInsensitive))
|
||||
{
|
||||
descriptionMatches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if group name matches then all should be visible
|
||||
if (group.name.contains(query, Qt::CaseInsensitive) ||
|
||||
descriptionMatches)
|
||||
{
|
||||
for (auto &&widget : group.widgets)
|
||||
widget.element->show();
|
||||
group.title->show();
|
||||
any = true;
|
||||
}
|
||||
// check if any match
|
||||
else
|
||||
{
|
||||
auto groupAny = false;
|
||||
|
||||
for (auto &&widget : group.widgets)
|
||||
{
|
||||
for (auto &&keyword : widget.keywords)
|
||||
{
|
||||
if (keyword.contains(query, Qt::CaseInsensitive))
|
||||
{
|
||||
widget.element->show();
|
||||
groupAny = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
widget.element->hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group.title->setVisible(groupAny);
|
||||
any |= groupAny;
|
||||
}
|
||||
}
|
||||
|
||||
return any;
|
||||
}
|
||||
|
||||
GeneralPage::GeneralPage()
|
||||
: SettingsPage("General", ":/settings/about.svg")
|
||||
{
|
||||
@@ -141,6 +198,7 @@ GeneralPage::GeneralPage()
|
||||
y->addWidget(scroll);
|
||||
auto x = new QHBoxLayout;
|
||||
auto layout = new SettingsLayout;
|
||||
this->settingsLayout_ = layout;
|
||||
x->addLayout(layout, 0);
|
||||
x->addStretch(1);
|
||||
auto z = new QFrame;
|
||||
@@ -155,6 +213,16 @@ GeneralPage::GeneralPage()
|
||||
this->initExtra();
|
||||
}
|
||||
|
||||
bool GeneralPage::filterElements(const QString &query)
|
||||
{
|
||||
if (this->settingsLayout_)
|
||||
return this->settingsLayout_->filterElements(query) ||
|
||||
this->name_.contains(query, Qt::CaseInsensitive) ||
|
||||
query.isEmpty();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
{
|
||||
auto &s = *getSettings();
|
||||
@@ -220,10 +288,6 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
// layout.addCheckbox("Mark last message you read");
|
||||
// layout.addDropdown("Last read message style", {"Default"});
|
||||
layout.addCheckbox("Show deleted messages", s.hideModerated, true);
|
||||
layout.addCheckbox("Show moderation messages", s.hideModerationActions,
|
||||
true);
|
||||
layout.addCheckbox("Random username color for users who never set a color",
|
||||
s.colorizeNicknames);
|
||||
layout.addDropdown<QString>(
|
||||
"Timestamps", {"Disable", "h:mm", "hh:mm", "h:mm a", "hh:mm a"},
|
||||
s.timestampFormat,
|
||||
@@ -268,6 +332,11 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
return QString::number(val) + "x";
|
||||
},
|
||||
[](auto args) { return fuzzyToFloat(args.value, 1.f); });
|
||||
|
||||
layout.addDropdown<int>(
|
||||
"Preview on hover", {"Don't show", "Always show", "Hold shift"},
|
||||
s.emotesTooltipPreview, [](int index) { return index; },
|
||||
[](auto args) { return args.index; }, false);
|
||||
layout.addDropdown("Emoji set",
|
||||
{"EmojiOne 2", "EmojiOne 3", "Twitter", "Facebook",
|
||||
"Apple", "Google", "Messenger"},
|
||||
@@ -284,15 +353,40 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
layout.addCheckbox("Chatterino", getSettings()->showBadgesChatterino);
|
||||
|
||||
layout.addTitle("Chat title");
|
||||
layout.addWidget(new QLabel("In live channels show:"));
|
||||
layout.addDescription("In live channels show:");
|
||||
layout.addCheckbox("Uptime", s.headerUptime);
|
||||
layout.addCheckbox("Viewer count", s.headerViewerCount);
|
||||
layout.addCheckbox("Category", s.headerGame);
|
||||
layout.addCheckbox("Title", s.headerStreamTitle);
|
||||
|
||||
layout.addTitle("Beta");
|
||||
layout.addDescription(
|
||||
"You can receive updates earlier by ticking the box below. Report "
|
||||
"issues <a href='https://chatterino.com/link/issues'>here</a>.");
|
||||
layout.addCheckbox("Receive beta updates", s.betaUpdates);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
layout.addTitle("Browser Integration");
|
||||
layout.addDescription("The browser extension replaces the default "
|
||||
"Twitch.tv chat with chatterino.");
|
||||
|
||||
layout.addDescription(
|
||||
createNamedLink(CHROME_EXTENSION_LINK, "Download for Google Chrome"));
|
||||
layout.addDescription(
|
||||
createNamedLink(FIREFOX_EXTENSION_LINK, "Download for Firefox"));
|
||||
#endif
|
||||
|
||||
layout.addTitle("Miscellaneous");
|
||||
|
||||
//layout.addWidget(makeOpenSettingDirButton());
|
||||
if (supportsIncognitoLinks())
|
||||
{
|
||||
layout.addCheckbox("Open links in incognito/private mode",
|
||||
s.openLinksIncognito);
|
||||
}
|
||||
layout.addCheckbox("Show moderation messages", s.hideModerationActions,
|
||||
true);
|
||||
layout.addCheckbox("Random username color for users who never set a color",
|
||||
s.colorizeNicknames);
|
||||
layout.addCheckbox("Mention users with a comma (User,)",
|
||||
s.mentionUsersWithComma);
|
||||
layout.addCheckbox("Show joined users (< 1000 chatters)", s.showJoins);
|
||||
@@ -314,11 +408,6 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
s.linksDoubleClickOnly);
|
||||
layout.addCheckbox("Unshorten links", s.unshortLinks);
|
||||
layout.addCheckbox("Show live indicator in tabs", s.showTabLive);
|
||||
layout.addDropdown<int>("Show emote preview in tooltip on hover",
|
||||
{"Don't show", "Always show", "Hold shift"},
|
||||
s.emotesTooltipPreview,
|
||||
[](int index) { return index; },
|
||||
[](auto args) { return args.index; }, false);
|
||||
|
||||
layout.addCheckbox(
|
||||
"Only search for emote autocompletion at the start of emote names",
|
||||
@@ -330,25 +419,57 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||
layout.addCheckbox("Load message history on connect",
|
||||
s.loadTwitchMessageHistoryOnConnect);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
layout.addTitle("Browser Integration");
|
||||
layout.addDescription("The browser extension replaces the default "
|
||||
"Twitch.tv chat with chatterino.");
|
||||
layout.addTitle("Cache");
|
||||
layout.addDescription(
|
||||
"Files that are used often (such as emotes) are saved to disk to "
|
||||
"reduce bandwidth usage and tho speed up loading.");
|
||||
|
||||
layout.addDescription(
|
||||
createNamedLink(CHROME_EXTENSION_LINK, "Download for Google Chrome"));
|
||||
layout.addDescription(
|
||||
createNamedLink(FIREFOX_EXTENSION_LINK, "Download for Firefox"));
|
||||
#endif
|
||||
} // namespace chatterino
|
||||
auto cachePathLabel = layout.addDescription("placeholder :D");
|
||||
getSettings()->cachePath.connect([cachePathLabel](const auto &,
|
||||
auto) mutable {
|
||||
QString newPath = getPaths()->cacheDirectory();
|
||||
|
||||
QString pathShortened = "Cache saved at <a href=\"file:///" + newPath +
|
||||
"\"><span style=\"color: white;\">" +
|
||||
shortenString(newPath, 50) + "</span></a>";
|
||||
cachePathLabel->setText(pathShortened);
|
||||
cachePathLabel->setToolTip(newPath);
|
||||
});
|
||||
|
||||
// Choose and reset buttons
|
||||
{
|
||||
auto box = new QHBoxLayout;
|
||||
|
||||
box->addWidget(layout.makeButton("Choose cache path", [this]() {
|
||||
getSettings()->cachePath = QFileDialog::getExistingDirectory(this);
|
||||
}));
|
||||
box->addWidget(layout.makeButton(
|
||||
"Reset", []() { getSettings()->cachePath = ""; }));
|
||||
box->addStretch(1);
|
||||
|
||||
layout.addLayout(box);
|
||||
}
|
||||
|
||||
layout.addTitle("AppData");
|
||||
layout.addDescription("All local files like settings and cache files are "
|
||||
"store in this directory.");
|
||||
layout.addButton("Open AppData directory", [] {
|
||||
QDesktopServices::openUrl(getPaths()->rootAppDataDirectory);
|
||||
});
|
||||
|
||||
// invisible element for width
|
||||
auto inv = new BaseWidget(this);
|
||||
inv->setScaleIndependantWidth(500);
|
||||
layout.addWidget(inv);
|
||||
}
|
||||
|
||||
void GeneralPage::initExtra()
|
||||
{
|
||||
/// update cache path
|
||||
if (this->cachePath)
|
||||
if (this->cachePath_)
|
||||
{
|
||||
getSettings()->cachePath.connect(
|
||||
[cachePath = this->cachePath](const auto &, auto) mutable {
|
||||
[cachePath = this->cachePath_](const auto &, auto) mutable {
|
||||
QString newPath = getPaths()->cacheDirectory();
|
||||
|
||||
QString pathShortened = "Current location: <a href=\"file:///" +
|
||||
|
||||
@@ -28,17 +28,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class TitleLabel2 : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TitleLabel2(const QString &text)
|
||||
: QLabel(text)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class DescriptionLabel : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -71,7 +60,6 @@ class SettingsLayout : public QVBoxLayout
|
||||
|
||||
public:
|
||||
TitleLabel *addTitle(const QString &text);
|
||||
TitleLabel2 *addTitle2(const QString &text);
|
||||
/// @param inverse Inverses true to false and vice versa
|
||||
QCheckBox *addCheckbox(const QString &text, BoolSetting &setting,
|
||||
bool inverse = false);
|
||||
@@ -80,6 +68,26 @@ public:
|
||||
pajlada::Settings::Setting<QString> &setting,
|
||||
bool editable = false);
|
||||
|
||||
template <typename OnClick>
|
||||
QPushButton *makeButton(const QString &text, OnClick onClick)
|
||||
{
|
||||
auto button = new QPushButton(text);
|
||||
this->groups_.back().widgets.push_back({button, {text}});
|
||||
QObject::connect(button, &QPushButton::clicked, onClick);
|
||||
return button;
|
||||
}
|
||||
|
||||
template <typename OnClick>
|
||||
QPushButton *addButton(const QString &text, OnClick onClick)
|
||||
{
|
||||
auto button = makeButton(text, onClick);
|
||||
auto layout = new QHBoxLayout();
|
||||
layout->addWidget(button);
|
||||
layout->addStretch(1);
|
||||
this->addLayout(layout);
|
||||
return button;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ComboBox *addDropdown(
|
||||
const QString &text, const QStringList &items,
|
||||
@@ -141,9 +149,23 @@ public:
|
||||
return combo;
|
||||
}
|
||||
DescriptionLabel *addDescription(const QString &text);
|
||||
|
||||
void addSeperator();
|
||||
bool filterElements(const QString &query);
|
||||
|
||||
private:
|
||||
struct Widget {
|
||||
QWidget *element;
|
||||
QStringList keywords;
|
||||
};
|
||||
|
||||
struct Group {
|
||||
QString name;
|
||||
QWidget *title{};
|
||||
std::vector<Widget> widgets;
|
||||
};
|
||||
|
||||
std::vector<Group> groups_;
|
||||
std::vector<pajlada::Signals::ScopedConnection> managedConnections_;
|
||||
};
|
||||
|
||||
@@ -154,13 +176,16 @@ class GeneralPage : public SettingsPage
|
||||
public:
|
||||
GeneralPage();
|
||||
|
||||
bool filterElements(const QString &query);
|
||||
|
||||
private:
|
||||
void initLayout(SettingsLayout &layout);
|
||||
void initExtra();
|
||||
|
||||
QString getFont(const DropdownArgs &args) const;
|
||||
|
||||
DescriptionLabel *cachePath{};
|
||||
DescriptionLabel *cachePath_{};
|
||||
SettingsLayout *settingsLayout_{};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -57,7 +57,7 @@ HighlightingPage::HighlightingPage()
|
||||
|
||||
view->addRegexHelpLink();
|
||||
view->setTitles({"Pattern", "Flash\ntaskbar", "Play\nsound",
|
||||
"Enable\nregex"});
|
||||
"Enable\nregex", "Case-\nsensitive"});
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
QHeaderView::Fixed);
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
@@ -71,8 +71,8 @@ HighlightingPage::HighlightingPage()
|
||||
});
|
||||
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()->highlights->phrases.appendItem(
|
||||
HighlightPhrase{"my phrase", true, false, false});
|
||||
getApp()->highlights->phrases.appendItem(HighlightPhrase{
|
||||
"my phrase", true, false, false, false});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,6 +87,10 @@ HighlightingPage::HighlightingPage()
|
||||
.getElement();
|
||||
|
||||
view->addRegexHelpLink();
|
||||
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
|
||||
view->setTitles({"Username", "Flash\ntaskbar", "Play\nsound",
|
||||
"Enable\nregex"});
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
@@ -103,7 +107,7 @@ HighlightingPage::HighlightingPage()
|
||||
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()->highlights->highlightedUsers.appendItem(
|
||||
HighlightPhrase{"highlighted user", true, false,
|
||||
HighlightPhrase{"highlighted user", true, false, false,
|
||||
false});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ KeyboardSettingsPage::KeyboardSettingsPage()
|
||||
auto layout =
|
||||
LayoutCreator<KeyboardSettingsPage>(this).setLayoutType<QVBoxLayout>();
|
||||
|
||||
auto form = layout.emplace<QFormLayout>();
|
||||
auto form = layout.emplace<QFormLayout>().withoutMargin();
|
||||
|
||||
form->addRow(new QLabel("Hold Ctrl"), new QLabel("Show resize handles"));
|
||||
form->addRow(new QLabel("Hold Ctrl + Alt"),
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
//#include "LogsPage.hpp"
|
||||
|
||||
//#include "Application.hpp"
|
||||
//#include "singletons/PathManager.hpp"
|
||||
|
||||
//#include <QFormLayout>
|
||||
//#include <QVBoxLayout>
|
||||
|
||||
//#include "util/LayoutCreator.hpp"
|
||||
|
||||
// namespace chatterino {
|
||||
// namespace widgets {
|
||||
// namespace settingspages {
|
||||
|
||||
// inline QString CreateLink(const QString &url, bool file = false)
|
||||
//{
|
||||
// if (file) {
|
||||
// return QString("<a href=\"file:///" + url + "\"><span style=\"color:
|
||||
// white;\">" + url +
|
||||
// "</span></a>");
|
||||
// }
|
||||
|
||||
// return QString("<a href=\"" + url + "\"><span style=\"color: white;\">" +
|
||||
// url +
|
||||
// "</span></a>");
|
||||
//}
|
||||
|
||||
// LogsPage::LogsPage()
|
||||
// : SettingsPage("Logs", "")
|
||||
//{
|
||||
// auto app = getApp();
|
||||
|
||||
// LayoutCreator<LogsPage> layoutCreator(this);
|
||||
// auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
|
||||
|
||||
// auto logPath = getPaths()->logsFolderPath;
|
||||
|
||||
// auto created = layout.emplace<QLabel>();
|
||||
// created->setText("Logs are saved to " + CreateLink(logPath, true));
|
||||
// created->setTextFormat(Qt::RichText);
|
||||
// created->setTextInteractionFlags(Qt::TextBrowserInteraction |
|
||||
// Qt::LinksAccessibleByKeyboard |
|
||||
// Qt::LinksAccessibleByKeyboard);
|
||||
// created->setOpenExternalLinks(true);
|
||||
// layout.append(this->createCheckBox("Enable logging",
|
||||
// getSettings()->enableLogging));
|
||||
|
||||
// layout->addStretch(1);
|
||||
//}
|
||||
|
||||
//} // namespace settingspages
|
||||
//} // namespace widgets
|
||||
//} // namespace chatterino
|
||||
@@ -1,17 +0,0 @@
|
||||
//#pragma once
|
||||
|
||||
//#include "widgets/settingspages/SettingsPage.hpp"
|
||||
|
||||
// namespace chatterino {
|
||||
// namespace widgets {
|
||||
// namespace settingspages {
|
||||
|
||||
// class LogsPage : public SettingsPage
|
||||
//{
|
||||
// public:
|
||||
// LogsPage();
|
||||
//};
|
||||
|
||||
//} // namespace settingspages
|
||||
//} // namespace widgets
|
||||
//} // namespace chatterino
|
||||
@@ -1,596 +0,0 @@
|
||||
#include "LookPage.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "messages/Image.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "util/RemoveScrollAreaBackground.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/helper/Line.hpp"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QFontDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSlider>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#define THEME_ITEMS "White", "Light", "Dark", "Black", "Custom"
|
||||
|
||||
#define TAB_X "Show tab close button"
|
||||
#define TAB_PREF "Hide preferences button (ctrl+p to show)"
|
||||
#define TAB_USER "Hide user button"
|
||||
|
||||
// clang-format off
|
||||
#define TIMESTAMP_FORMATS "hh:mm a", "h:mm a", "hh:mm:ss a", "h:mm:ss a", "HH:mm", "H:mm", "HH:mm:ss", "H:mm:ss"
|
||||
// clang-format on
|
||||
|
||||
#ifdef USEWINSDK
|
||||
# define WINDOW_TOPMOST "Window always on top"
|
||||
#else
|
||||
# define WINDOW_TOPMOST "Window always on top (requires restart)"
|
||||
#endif
|
||||
#define INPUT_EMPTY "Show input box when empty"
|
||||
#define LAST_MSG "Mark the last message you read"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
LookPage::LookPage()
|
||||
: SettingsPage("Look", ":/settings/theme.svg")
|
||||
{
|
||||
this->initializeUi();
|
||||
}
|
||||
|
||||
void LookPage::initializeUi()
|
||||
{
|
||||
LayoutCreator<LookPage> layoutCreator(this);
|
||||
|
||||
auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
|
||||
|
||||
// settings
|
||||
auto tabs = layout.emplace<QTabWidget>();
|
||||
|
||||
this->addInterfaceTab(tabs.appendTab(new QVBoxLayout, "Interface"));
|
||||
this->addMessageTab(tabs.appendTab(new QVBoxLayout, "Messages"));
|
||||
this->addEmoteTab(tabs.appendTab(new QVBoxLayout, "Emotes"));
|
||||
this->addSplitHeaderTab(tabs.appendTab(new QVBoxLayout, "Split header"));
|
||||
this->addBadgesTab(tabs.appendTab(new QVBoxLayout, "Badges"));
|
||||
|
||||
layout->addStretch(1);
|
||||
|
||||
// preview
|
||||
layout.emplace<Line>(false);
|
||||
|
||||
auto channelView = layout.emplace<ChannelView>();
|
||||
auto channel = this->createPreviewChannel();
|
||||
channelView->setChannel(channel);
|
||||
channelView->setScaleIndependantHeight(74);
|
||||
}
|
||||
|
||||
void LookPage::addInterfaceTab(LayoutCreator<QVBoxLayout> layout)
|
||||
{
|
||||
// theme
|
||||
{
|
||||
auto *theme =
|
||||
this->createComboBox({THEME_ITEMS}, getApp()->themes->themeName);
|
||||
QDoubleSpinBox *w = new QDoubleSpinBox;
|
||||
|
||||
QObject::connect(theme, &QComboBox::currentTextChanged,
|
||||
[w](const QString &themeName) {
|
||||
if (themeName == "Custom")
|
||||
{
|
||||
w->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
w->hide();
|
||||
}
|
||||
getApp()->windows->forceLayoutChannelViews();
|
||||
});
|
||||
|
||||
auto box = layout.emplace<QHBoxLayout>().withoutMargin();
|
||||
box.emplace<QLabel>("Theme: ");
|
||||
box.append(theme);
|
||||
|
||||
{
|
||||
w->setButtonSymbols(QDoubleSpinBox::NoButtons);
|
||||
if (getApp()->themes->themeName.getValue() != "Custom")
|
||||
{
|
||||
w->hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
w->show();
|
||||
}
|
||||
w->setRange(-1.0, 1.0);
|
||||
w->setSingleStep(0.05);
|
||||
w->setValue(getSettings()->customThemeMultiplier.getValue());
|
||||
|
||||
QObject::connect(
|
||||
w, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
|
||||
[](double value) {
|
||||
getSettings()->customThemeMultiplier.setValue(float(value));
|
||||
getApp()->themes->update();
|
||||
getApp()->windows->forceLayoutChannelViews();
|
||||
});
|
||||
box.append(w);
|
||||
}
|
||||
|
||||
box->addStretch(1);
|
||||
}
|
||||
|
||||
layout.append(
|
||||
this->createCheckBox(WINDOW_TOPMOST, getSettings()->windowTopMost));
|
||||
|
||||
// --
|
||||
layout.emplace<Line>(false);
|
||||
|
||||
// tab x
|
||||
layout.append(
|
||||
this->createCheckBox(TAB_X, getSettings()->showTabCloseButton));
|
||||
|
||||
// show buttons
|
||||
#ifndef USEWINSDK
|
||||
layout.append(
|
||||
this->createCheckBox(TAB_PREF, getSettings()->hidePreferencesButton));
|
||||
layout.append(
|
||||
this->createCheckBox(TAB_USER, getSettings()->hideUserButton));
|
||||
#endif
|
||||
|
||||
// empty input
|
||||
layout.append(
|
||||
this->createCheckBox(INPUT_EMPTY, getSettings()->showEmptyInput));
|
||||
layout.append(this->createCheckBox("Show message length while typing",
|
||||
getSettings()->showMessageLength));
|
||||
layout->addStretch(1);
|
||||
}
|
||||
|
||||
void LookPage::addMessageTab(LayoutCreator<QVBoxLayout> layout)
|
||||
{
|
||||
// font
|
||||
layout.append(this->createFontChanger());
|
||||
|
||||
// --
|
||||
layout.emplace<Line>(false);
|
||||
|
||||
// timestamps
|
||||
{
|
||||
auto box = layout.emplace<QHBoxLayout>().withoutMargin();
|
||||
box.append(this->createCheckBox("Show timestamps",
|
||||
getSettings()->showTimestamps));
|
||||
box.append(this->createComboBox({TIMESTAMP_FORMATS},
|
||||
getSettings()->timestampFormat));
|
||||
box->addStretch(1);
|
||||
}
|
||||
|
||||
// --
|
||||
layout.emplace<Line>(false);
|
||||
|
||||
// separate
|
||||
layout.append(this->createCheckBox("Lines between messages",
|
||||
getSettings()->separateMessages));
|
||||
|
||||
// alternate
|
||||
layout.append(this->createCheckBox("Alternate background",
|
||||
getSettings()->alternateMessages));
|
||||
|
||||
layout.append(
|
||||
this->createCheckBox("Compact emotes", getSettings()->compactEmotes));
|
||||
layout.emplace<Line>(false);
|
||||
|
||||
// bold-slider
|
||||
{
|
||||
auto box = layout.emplace<QHBoxLayout>().withoutMargin();
|
||||
box.emplace<QLabel>("Username boldness: ");
|
||||
box.append(this->createBoldScaleSlider());
|
||||
}
|
||||
|
||||
// bold usernames
|
||||
layout.append(this->createCheckBox("Bold mentions (@username)",
|
||||
getSettings()->boldUsernames));
|
||||
|
||||
// --
|
||||
layout.emplace<Line>(false);
|
||||
|
||||
// lowercase links
|
||||
layout.append(this->createCheckBox("Lowercase domains",
|
||||
getSettings()->lowercaseDomains));
|
||||
|
||||
// collapsing
|
||||
{
|
||||
auto *combo = new QComboBox(this);
|
||||
combo->addItems({"Never", "2", "3", "4", "5", "6", "7", "8", "9", "10",
|
||||
"11", "12", "13", "14", "15"});
|
||||
|
||||
const auto currentIndex = []() -> int {
|
||||
auto val = getSettings()->collpseMessagesMinLines.getValue();
|
||||
if (val > 0)
|
||||
{
|
||||
--val;
|
||||
}
|
||||
return val;
|
||||
}();
|
||||
combo->setCurrentIndex(currentIndex);
|
||||
|
||||
QObject::connect(
|
||||
combo, &QComboBox::currentTextChanged, [](const QString &str) {
|
||||
getSettings()->collpseMessagesMinLines = str.toInt();
|
||||
});
|
||||
|
||||
auto hbox = layout.emplace<QHBoxLayout>().withoutMargin();
|
||||
hbox.emplace<QLabel>("Collapse messages longer than");
|
||||
hbox.append(combo);
|
||||
hbox.emplace<QLabel>("lines");
|
||||
}
|
||||
|
||||
// last read message
|
||||
this->addLastReadMessageIndicatorPatternSelector(layout);
|
||||
|
||||
// --
|
||||
layout->addStretch(1);
|
||||
}
|
||||
|
||||
void LookPage::addEmoteTab(LayoutCreator<QVBoxLayout> layout)
|
||||
{
|
||||
layout.append(
|
||||
this->createCheckBox("Animations", getSettings()->animateEmotes));
|
||||
layout.append(
|
||||
this->createCheckBox("Animations only when chatterino has focus",
|
||||
getSettings()->animationsWhenFocused));
|
||||
|
||||
auto scaleBox = layout.emplace<QHBoxLayout>().withoutMargin();
|
||||
{
|
||||
scaleBox.emplace<QLabel>("Size:");
|
||||
|
||||
auto emoteScale = scaleBox.emplace<QSlider>(Qt::Horizontal);
|
||||
emoteScale->setMinimum(5);
|
||||
emoteScale->setMaximum(50);
|
||||
|
||||
auto scaleLabel = scaleBox.emplace<QLabel>("1.0");
|
||||
scaleLabel->setFixedWidth(100);
|
||||
QObject::connect(emoteScale.getElement(), &QSlider::valueChanged,
|
||||
[scaleLabel](int value) mutable {
|
||||
float f = float(value) / 10.f;
|
||||
scaleLabel->setText(QString::number(f));
|
||||
|
||||
getSettings()->emoteScale.setValue(f);
|
||||
});
|
||||
|
||||
emoteScale->setValue(std::max<int>(
|
||||
5, std::min<int>(
|
||||
50, int(getSettings()->emoteScale.getValue() * 10.f))));
|
||||
|
||||
scaleLabel->setText(
|
||||
QString::number(getSettings()->emoteScale.getValue()));
|
||||
}
|
||||
|
||||
{
|
||||
auto *combo = new QComboBox(this);
|
||||
combo->addItems({"EmojiOne 2", "EmojiOne 3", "Twitter", "Facebook",
|
||||
"Apple", "Google", "Messenger"});
|
||||
|
||||
combo->setCurrentText(getSettings()->emojiSet);
|
||||
|
||||
QObject::connect(combo, &QComboBox::currentTextChanged,
|
||||
[](const QString &str) {
|
||||
getSettings()->emojiSet = str; //
|
||||
});
|
||||
|
||||
auto hbox = layout.emplace<QHBoxLayout>().withoutMargin();
|
||||
hbox.emplace<QLabel>("Emoji set:");
|
||||
hbox.append(combo);
|
||||
}
|
||||
|
||||
layout->addStretch(1);
|
||||
}
|
||||
|
||||
void LookPage::addSplitHeaderTab(LayoutCreator<QVBoxLayout> layout)
|
||||
{
|
||||
layout.append(
|
||||
this->createCheckBox("Show uptime", getSettings()->headerUptime));
|
||||
layout.append(this->createCheckBox("Show viewer count",
|
||||
getSettings()->headerViewerCount));
|
||||
layout.append(this->createCheckBox("Show game", getSettings()->headerGame));
|
||||
layout.append(
|
||||
this->createCheckBox("Show title", getSettings()->headerStreamTitle));
|
||||
|
||||
layout->addStretch(1);
|
||||
}
|
||||
|
||||
void LookPage::addBadgesTab(LayoutCreator<QVBoxLayout> layout)
|
||||
{
|
||||
// layout.append(
|
||||
// this->createCheckBox(("Show all badges"), getSettings()->showBadges));
|
||||
auto fastSelection = layout.emplace<QHBoxLayout>();
|
||||
{
|
||||
auto addAll = fastSelection.emplace<QPushButton>("Enable all");
|
||||
QObject::connect(addAll.getElement(), &QPushButton::clicked, this, [] {
|
||||
getSettings()->showBadgesGlobalAuthority = true;
|
||||
getSettings()->showBadgesChannelAuthority = true;
|
||||
getSettings()->showBadgesSubscription = true;
|
||||
getSettings()->showBadgesVanity = true;
|
||||
getSettings()->showBadgesChatterino = true;
|
||||
});
|
||||
auto removeAll = fastSelection.emplace<QPushButton>("Disable all");
|
||||
QObject::connect(removeAll.getElement(), &QPushButton::clicked, this,
|
||||
[] {
|
||||
getSettings()->showBadgesGlobalAuthority = false;
|
||||
getSettings()->showBadgesChannelAuthority = false;
|
||||
getSettings()->showBadgesSubscription = false;
|
||||
getSettings()->showBadgesVanity = false;
|
||||
getSettings()->showBadgesChatterino = false;
|
||||
});
|
||||
}
|
||||
layout.emplace<Line>(false);
|
||||
layout.append(this->createCheckBox(
|
||||
("Show authority badges (staff, admin, turbo, etc)"),
|
||||
getSettings()->showBadgesGlobalAuthority));
|
||||
layout.append(this->createCheckBox(
|
||||
("Show channel badges (broadcaster, moderator, VIP)"),
|
||||
getSettings()->showBadgesChannelAuthority));
|
||||
layout.append(this->createCheckBox(("Show subscriber badges "),
|
||||
getSettings()->showBadgesSubscription));
|
||||
layout.append(
|
||||
this->createCheckBox(("Show vanity badges (prime, bits, subgifter)"),
|
||||
getSettings()->showBadgesVanity));
|
||||
layout.append(this->createCheckBox(("Show chatterino badges"),
|
||||
getSettings()->showBadgesChatterino));
|
||||
layout->addStretch(1);
|
||||
}
|
||||
|
||||
void LookPage::addLastReadMessageIndicatorPatternSelector(
|
||||
LayoutCreator<QVBoxLayout> layout)
|
||||
{
|
||||
// combo
|
||||
auto *combo = new QComboBox(this);
|
||||
combo->addItems({"Dotted line", "Solid line"});
|
||||
|
||||
const auto currentIndex = []() -> int {
|
||||
switch (getSettings()->lastMessagePattern.getValue())
|
||||
{
|
||||
case Qt::SolidLine:
|
||||
return 1;
|
||||
case Qt::VerPattern:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}();
|
||||
combo->setCurrentIndex(currentIndex);
|
||||
|
||||
QObject::connect(
|
||||
combo,
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
[](int index) {
|
||||
getSettings()->lastMessagePattern = [&] {
|
||||
switch (index)
|
||||
{
|
||||
case 1:
|
||||
return Qt::SolidPattern;
|
||||
case 0:
|
||||
default:
|
||||
return Qt::VerPattern;
|
||||
}
|
||||
}();
|
||||
});
|
||||
|
||||
// color picker
|
||||
|
||||
QLabel *colorPreview = new QLabel();
|
||||
|
||||
auto updatePreviewColor = [colorPreview](QColor newColor) {
|
||||
QPixmap pixmap(16, 16);
|
||||
pixmap.fill(QColor(0, 0, 0, 255));
|
||||
|
||||
QPainter painter(&pixmap);
|
||||
QBrush brush(newColor);
|
||||
painter.fillRect(1, 1, pixmap.width() - 2, pixmap.height() - 2, brush);
|
||||
colorPreview->setPixmap(pixmap);
|
||||
};
|
||||
|
||||
auto getCurrentColor = []() {
|
||||
return getSettings()->lastMessageColor != ""
|
||||
? QColor(getSettings()->lastMessageColor.getValue())
|
||||
: getApp()
|
||||
->themes->tabs.selected.backgrounds.regular.color();
|
||||
};
|
||||
|
||||
updatePreviewColor(getCurrentColor());
|
||||
|
||||
QPushButton *button = new QPushButton("Select Color");
|
||||
button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Policy::Fixed);
|
||||
|
||||
QObject::connect(
|
||||
button, &QPushButton::clicked, [updatePreviewColor, getCurrentColor]() {
|
||||
QColor newColor = QColorDialog::getColor(getCurrentColor());
|
||||
if (newColor.isValid())
|
||||
{
|
||||
updatePreviewColor(newColor);
|
||||
getSettings()->lastMessageColor = newColor.name();
|
||||
}
|
||||
});
|
||||
|
||||
QPushButton *resetButton = new QPushButton("Reset Color");
|
||||
resetButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Policy::Fixed);
|
||||
|
||||
QObject::connect(
|
||||
resetButton, &QPushButton::clicked, [updatePreviewColor]() {
|
||||
QColor defaultColor =
|
||||
getApp()->themes->tabs.selected.backgrounds.regular.color();
|
||||
updatePreviewColor(defaultColor);
|
||||
getSettings()->lastMessageColor = "";
|
||||
});
|
||||
|
||||
// layout
|
||||
auto hbox = layout.emplace<QHBoxLayout>().withoutMargin();
|
||||
hbox.append(this->createCheckBox(LAST_MSG,
|
||||
getSettings()->showLastMessageIndicator));
|
||||
hbox.append(combo);
|
||||
hbox.append(colorPreview);
|
||||
hbox.append(button);
|
||||
hbox.append(resetButton);
|
||||
hbox->addStretch(1);
|
||||
}
|
||||
|
||||
ChannelPtr LookPage::createPreviewChannel()
|
||||
{
|
||||
auto channel = ChannelPtr(new Channel("preview", Channel::Type::Misc));
|
||||
|
||||
// clang-format off
|
||||
{
|
||||
MessageBuilder builder;
|
||||
builder.emplace<TimestampElement>(QTime(8, 13, 42));
|
||||
builder.emplace<ImageElement>(Image::fromPixmap(getApp()->resources->twitch.moderator), MessageElementFlag::BadgeChannelAuthority);
|
||||
builder.emplace<ImageElement>(Image::fromPixmap(getApp()->resources->twitch.subscriber, 0.25), MessageElementFlag::BadgeSubscription);
|
||||
builder.emplace<TextElement>("username1:", MessageElementFlag::Username, QColor("#0094FF"), FontStyle::ChatMediumBold);
|
||||
builder.emplace<TextElement>("This is a preview message", MessageElementFlag::Text);
|
||||
builder.emplace<ImageElement>(Image::fromPixmap(getApp()->resources->pajaDank, 0.25), MessageElementFlag::AlwaysShow);
|
||||
builder.emplace<TextElement>("@fourtf", MessageElementFlag::BoldUsername, MessageColor::Text, FontStyle::ChatMediumBold);
|
||||
builder.emplace<TextElement>("@fourtf", MessageElementFlag::NonBoldUsername);
|
||||
channel->addMessage(builder.release());
|
||||
}
|
||||
{
|
||||
MessageBuilder message;
|
||||
message.emplace<TimestampElement>(QTime(8, 15, 21));
|
||||
message.emplace<ImageElement>(Image::fromPixmap(getApp()->resources->twitch.broadcaster), MessageElementFlag::BadgeChannelAuthority);
|
||||
message.emplace<TextElement>("username2:", MessageElementFlag::Username, QColor("#FF6A00"), FontStyle::ChatMediumBold);
|
||||
message.emplace<TextElement>("This is another one", MessageElementFlag::Text);
|
||||
// message.emplace<ImageElement>(Image::fromNonOwningPixmap(&getApp()->resources->ppHop), MessageElementFlag::BttvEmote);
|
||||
message.emplace<TextElement>("www.fourtf.com", MessageElementFlag::LowercaseLink, MessageColor::Link)->setLink(Link(Link::Url, "https://www.fourtf.com"));
|
||||
message.emplace<TextElement>("wWw.FoUrTf.CoM", MessageElementFlag::OriginalLink, MessageColor::Link)->setLink(Link(Link::Url, "https://www.fourtf.com"));
|
||||
channel->addMessage(message.release());
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
QLayout *LookPage::createThemeColorChanger()
|
||||
{
|
||||
auto app = getApp();
|
||||
QHBoxLayout *layout = new QHBoxLayout;
|
||||
|
||||
auto &themeHue = app->themes->themeHue;
|
||||
|
||||
// SLIDER
|
||||
QSlider *slider = new QSlider(Qt::Horizontal);
|
||||
layout->addWidget(slider);
|
||||
slider->setValue(
|
||||
int(std::min(std::max(themeHue.getValue(), 0.0), 1.0) * 100));
|
||||
|
||||
// BUTTON
|
||||
QPushButton *button = new QPushButton;
|
||||
layout->addWidget(button);
|
||||
button->setFlat(true);
|
||||
button->setFixedWidth(64);
|
||||
|
||||
auto setButtonColor = [button, app](int value) mutable {
|
||||
double newValue = value / 100.0;
|
||||
app->themes->themeHue.setValue(newValue);
|
||||
|
||||
QPalette pal = button->palette();
|
||||
QColor color;
|
||||
color.setHsvF(newValue, 1.0, 1.0, 1.0);
|
||||
pal.setColor(QPalette::Button, color);
|
||||
button->setAutoFillBackground(true);
|
||||
button->setPalette(pal);
|
||||
button->update();
|
||||
};
|
||||
|
||||
// SIGNALS
|
||||
QObject::connect(slider, &QSlider::valueChanged, this, setButtonColor);
|
||||
|
||||
setButtonColor(themeHue * 100);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
QLayout *LookPage::createFontChanger()
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout;
|
||||
layout->setMargin(0);
|
||||
|
||||
// LABEL
|
||||
QLabel *label = new QLabel();
|
||||
layout->addWidget(label);
|
||||
|
||||
auto updateFontFamilyLabel = [=]() {
|
||||
label->setText("Font (" + app->fonts->chatFontFamily.getValue() + ", " +
|
||||
QString::number(app->fonts->chatFontSize) + "pt)");
|
||||
};
|
||||
|
||||
app->fonts->chatFontFamily.connect(updateFontFamilyLabel,
|
||||
this->managedConnections_);
|
||||
app->fonts->chatFontSize.connect(updateFontFamilyLabel,
|
||||
this->managedConnections_);
|
||||
|
||||
// BUTTON
|
||||
QPushButton *button = new QPushButton("Select");
|
||||
layout->addWidget(button);
|
||||
button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Policy::Fixed);
|
||||
|
||||
QObject::connect(button, &QPushButton::clicked, [=]() {
|
||||
QFontDialog dialog(app->fonts->getFont(FontStyle::ChatMedium, 1.));
|
||||
|
||||
dialog.setWindowFlag(Qt::WindowStaysOnTopHint);
|
||||
|
||||
dialog.connect(&dialog, &QFontDialog::fontSelected,
|
||||
[=](const QFont &font) {
|
||||
app->fonts->chatFontFamily = font.family();
|
||||
app->fonts->chatFontSize = font.pointSize();
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
dialog.exec();
|
||||
});
|
||||
|
||||
layout->addStretch(1);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
QLayout *LookPage::createBoldScaleSlider()
|
||||
{
|
||||
auto layout = new QHBoxLayout();
|
||||
auto slider = new QSlider(Qt::Horizontal);
|
||||
auto label = new QLabel();
|
||||
|
||||
layout->addWidget(slider);
|
||||
layout->addWidget(label);
|
||||
|
||||
slider->setMinimum(50);
|
||||
slider->setMaximum(100);
|
||||
slider->setValue(getSettings()->boldScale.getValue());
|
||||
|
||||
label->setMinimumWidth(100);
|
||||
|
||||
QObject::connect(slider, &QSlider::valueChanged, [](auto value) {
|
||||
getSettings()->boldScale.setValue(value);
|
||||
});
|
||||
// show value
|
||||
// getSettings()->boldScale.connect(
|
||||
// [label](auto, auto) {
|
||||
// label->setText(QString::number(getSettings()->boldScale.getValue()));
|
||||
// },
|
||||
// this->connections_);
|
||||
|
||||
// QPushButton *button = new QPushButton("Reset");
|
||||
// layout->addWidget(button);
|
||||
// button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Policy::Fixed);
|
||||
|
||||
// QObject::connect(button, &QPushButton::clicked, [=]() {
|
||||
// getSettings()->boldScale.setValue(57);
|
||||
// slider->setValue(57);
|
||||
//});
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/settingspages/SettingsPage.hpp"
|
||||
|
||||
#include <QScrollArea>
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
class QVBoxLayout;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class LookPage : public SettingsPage
|
||||
{
|
||||
public:
|
||||
LookPage();
|
||||
|
||||
private:
|
||||
void initializeUi();
|
||||
|
||||
void addInterfaceTab(LayoutCreator<QVBoxLayout> layout);
|
||||
void addMessageTab(LayoutCreator<QVBoxLayout> layout);
|
||||
void addEmoteTab(LayoutCreator<QVBoxLayout> layout);
|
||||
void addSplitHeaderTab(LayoutCreator<QVBoxLayout> layout);
|
||||
void addBadgesTab(LayoutCreator<QVBoxLayout> layout);
|
||||
|
||||
void addLastReadMessageIndicatorPatternSelector(
|
||||
LayoutCreator<QVBoxLayout> layout);
|
||||
|
||||
QLayout *createThemeColorChanger();
|
||||
QLayout *createFontChanger();
|
||||
QLayout *createBoldScaleSlider();
|
||||
|
||||
ChannelPtr createPreviewChannel();
|
||||
|
||||
std::vector<pajlada::Signals::ScopedConnection> connections_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -83,13 +83,10 @@ ModerationPage::ModerationPage()
|
||||
|
||||
auto logs = tabs.appendTab(new QVBoxLayout, "Logs");
|
||||
{
|
||||
logs.append(this->createCheckBox("Enable logging",
|
||||
getSettings()->enableLogging));
|
||||
auto logsPathLabel = logs.emplace<QLabel>();
|
||||
|
||||
// Show how big (size-wise) the logs are
|
||||
auto logsPathSizeLabel = logs.emplace<QLabel>();
|
||||
logsPathSizeLabel->setText(
|
||||
QtConcurrent::run([] { return fetchLogDirectorySize(); }));
|
||||
|
||||
// Logs (copied from LoggingMananger)
|
||||
getSettings()->logPath.connect([logsPathLabel](const QString &logPath,
|
||||
auto) mutable {
|
||||
@@ -109,13 +106,27 @@ ModerationPage::ModerationPage()
|
||||
logsPathLabel->setTextInteractionFlags(Qt::TextBrowserInteraction |
|
||||
Qt::LinksAccessibleByKeyboard);
|
||||
logsPathLabel->setOpenExternalLinks(true);
|
||||
logs.append(this->createCheckBox("Enable logging",
|
||||
getSettings()->enableLogging));
|
||||
|
||||
auto buttons = logs.emplace<QHBoxLayout>().withoutMargin();
|
||||
|
||||
// Select and Reset
|
||||
auto selectDir = buttons.emplace<QPushButton>("Select log directory ");
|
||||
auto resetDir = buttons.emplace<QPushButton>("Reset");
|
||||
|
||||
getSettings()->logPath.connect(
|
||||
[element = resetDir.getElement()](const QString &path) {
|
||||
element->setEnabled(!path.isEmpty());
|
||||
});
|
||||
|
||||
buttons->addStretch();
|
||||
logs->addStretch(1);
|
||||
auto selectDir = logs.emplace<QPushButton>("Set custom logpath");
|
||||
|
||||
// Setting custom logpath
|
||||
// Show how big (size-wise) the logs are
|
||||
auto logsPathSizeLabel = logs.emplace<QLabel>();
|
||||
logsPathSizeLabel->setText(
|
||||
QtConcurrent::run([] { return fetchLogDirectorySize(); }));
|
||||
|
||||
// Select event
|
||||
QObject::connect(
|
||||
selectDir.getElement(), &QPushButton::clicked, this,
|
||||
[this, logsPathSizeLabel]() mutable {
|
||||
@@ -128,8 +139,9 @@ ModerationPage::ModerationPage()
|
||||
QtConcurrent::run([] { return fetchLogDirectorySize(); }));
|
||||
});
|
||||
|
||||
buttons->addSpacing(16);
|
||||
|
||||
// Reset custom logpath
|
||||
auto resetDir = logs.emplace<QPushButton>("Reset logpath");
|
||||
QObject::connect(resetDir.getElement(), &QPushButton::clicked, this,
|
||||
[logsPathSizeLabel]() mutable {
|
||||
getSettings()->logPath = "";
|
||||
@@ -139,8 +151,7 @@ ModerationPage::ModerationPage()
|
||||
[] { return fetchLogDirectorySize(); }));
|
||||
});
|
||||
|
||||
// Logs end
|
||||
}
|
||||
} // logs end
|
||||
|
||||
auto modMode = tabs.appendTab(new QVBoxLayout, "Moderation buttons");
|
||||
{
|
||||
|
||||
@@ -5,15 +5,77 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
#include <util/FunctionEventFilter.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
bool filterItemsRec(QObject *object, const QString &query)
|
||||
{
|
||||
bool any{};
|
||||
|
||||
for (auto &&child : object->children())
|
||||
{
|
||||
auto setOpacity = [&](auto *widget, bool condition) {
|
||||
any |= condition;
|
||||
widget->greyedOut = !condition;
|
||||
widget->update();
|
||||
};
|
||||
|
||||
if (auto x = dynamic_cast<SCheckBox *>(child); x)
|
||||
{
|
||||
setOpacity(x, x->text().contains(query, Qt::CaseInsensitive));
|
||||
}
|
||||
else if (auto x = dynamic_cast<SLabel *>(child); x)
|
||||
{
|
||||
setOpacity(x, x->text().contains(query, Qt::CaseInsensitive));
|
||||
}
|
||||
else if (auto x = dynamic_cast<SComboBox *>(child); x)
|
||||
{
|
||||
setOpacity(x, [=]() {
|
||||
for (int i = 0; i < x->count(); i++)
|
||||
{
|
||||
if (x->itemText(i).contains(query, Qt::CaseInsensitive))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}());
|
||||
}
|
||||
else if (auto x = dynamic_cast<QTabWidget *>(child); x)
|
||||
{
|
||||
for (int i = 0; i < x->count(); i++)
|
||||
{
|
||||
bool tabAny{};
|
||||
|
||||
if (x->tabText(i).contains(query, Qt::CaseInsensitive))
|
||||
{
|
||||
tabAny = true;
|
||||
}
|
||||
auto widget = x->widget(i);
|
||||
tabAny |= filterItemsRec(widget, query);
|
||||
|
||||
any |= tabAny;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
any |= filterItemsRec(child, query);
|
||||
}
|
||||
}
|
||||
return any;
|
||||
}
|
||||
|
||||
SettingsPage::SettingsPage(const QString &name, const QString &iconResource)
|
||||
: name_(name)
|
||||
, iconResource_(iconResource)
|
||||
{
|
||||
}
|
||||
|
||||
bool SettingsPage::filterElements(const QString &query)
|
||||
{
|
||||
return filterItemsRec(this, query) || query.isEmpty() ||
|
||||
this->name_.contains(query, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
const QString &SettingsPage::getName()
|
||||
{
|
||||
return this->name_;
|
||||
@@ -42,7 +104,7 @@ void SettingsPage::cancel()
|
||||
QCheckBox *SettingsPage::createCheckBox(
|
||||
const QString &text, pajlada::Settings::Setting<bool> &setting)
|
||||
{
|
||||
QCheckBox *checkbox = new QCheckBox(text);
|
||||
QCheckBox *checkbox = new SCheckBox(text);
|
||||
|
||||
// update when setting changes
|
||||
setting.connect(
|
||||
@@ -64,7 +126,7 @@ QCheckBox *SettingsPage::createCheckBox(
|
||||
QComboBox *SettingsPage::createComboBox(
|
||||
const QStringList &items, pajlada::Settings::Setting<QString> &setting)
|
||||
{
|
||||
QComboBox *combo = new QComboBox();
|
||||
QComboBox *combo = new SComboBox();
|
||||
|
||||
// update setting on toogle
|
||||
combo->addItems(items);
|
||||
|
||||
@@ -8,8 +8,38 @@
|
||||
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
#define SETTINGS_PAGE_WIDGET_BOILERPLATE(type, parent) \
|
||||
class type : public parent \
|
||||
{ \
|
||||
using parent::parent; \
|
||||
\
|
||||
public: \
|
||||
bool greyedOut{}; \
|
||||
\
|
||||
protected: \
|
||||
void paintEvent(QPaintEvent *e) override \
|
||||
{ \
|
||||
parent::paintEvent(e); \
|
||||
\
|
||||
if (this->greyedOut) \
|
||||
{ \
|
||||
QPainter painter(this); \
|
||||
QColor color = QColor("#222222"); \
|
||||
color.setAlphaF(0.7); \
|
||||
painter.fillRect(this->rect(), color); \
|
||||
} \
|
||||
} \
|
||||
};
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// S* widgets are the same as their Q* counterparts,
|
||||
// but they can be greyed out and will be if you search.
|
||||
SETTINGS_PAGE_WIDGET_BOILERPLATE(SCheckBox, QCheckBox)
|
||||
SETTINGS_PAGE_WIDGET_BOILERPLATE(SLabel, QLabel)
|
||||
SETTINGS_PAGE_WIDGET_BOILERPLATE(SComboBox, QComboBox)
|
||||
SETTINGS_PAGE_WIDGET_BOILERPLATE(SPushButton, QPushButton)
|
||||
|
||||
class SettingsDialogTab;
|
||||
|
||||
class SettingsPage : public QFrame
|
||||
@@ -22,6 +52,8 @@ public:
|
||||
const QString &getName();
|
||||
const QString &getIconResource();
|
||||
|
||||
virtual bool filterElements(const QString &query);
|
||||
|
||||
SettingsDialogTab *tab() const;
|
||||
void setTab(SettingsDialogTab *tab);
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#include "SpecialChannelsPage.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
SpecialChannelsPage::SpecialChannelsPage()
|
||||
: SettingsPage("Special channels", "")
|
||||
{
|
||||
LayoutCreator<SpecialChannelsPage> layoutCreator(this);
|
||||
auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
|
||||
|
||||
auto mentions = layout.emplace<QGroupBox>("Mentions channel")
|
||||
.setLayoutType<QVBoxLayout>();
|
||||
{
|
||||
mentions.emplace<QLabel>("Join /mentions to view your mentions.");
|
||||
}
|
||||
|
||||
auto whispers =
|
||||
layout.emplace<QGroupBox>("Whispers").setLayoutType<QVBoxLayout>();
|
||||
{
|
||||
whispers.emplace<QLabel>("Join /whispers to view your mentions.");
|
||||
}
|
||||
|
||||
layout->addStretch(1);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/settingspages/SettingsPage.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class SpecialChannelsPage : public SettingsPage
|
||||
{
|
||||
public:
|
||||
SpecialChannelsPage();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -89,6 +89,7 @@ Split::Split(QWidget *parent)
|
||||
{
|
||||
this->setMouseTracking(true);
|
||||
this->view_->setPausable(true);
|
||||
this->view_->setFocusPolicy(Qt::FocusPolicy::NoFocus);
|
||||
|
||||
this->vbox_->setSpacing(0);
|
||||
this->vbox_->setMargin(1);
|
||||
@@ -663,7 +664,10 @@ void Split::reloadChannelAndSubscriberEmotes()
|
||||
auto channel = this->getChannel();
|
||||
|
||||
if (auto twitchChannel = dynamic_cast<TwitchChannel *>(channel.get()))
|
||||
twitchChannel->refreshChannelEmotes();
|
||||
{
|
||||
twitchChannel->refreshBTTVChannelEmotes();
|
||||
twitchChannel->refreshFFZChannelEmotes();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iter, typename RandomGenerator>
|
||||
|
||||
@@ -189,7 +189,6 @@ void SplitHeader::initializeLayout()
|
||||
SettingsDialogPreference::
|
||||
ModerationActions);
|
||||
this->split_->setModerationMode(true);
|
||||
w->setDim(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -198,7 +197,7 @@ void SplitHeader::initializeLayout()
|
||||
|
||||
this->split_->setModerationMode(
|
||||
!moderationMode);
|
||||
w->setDim(moderationMode);
|
||||
w->setDim(Button::Dim(moderationMode));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -713,7 +712,10 @@ void SplitHeader::reloadChannelEmotes()
|
||||
auto channel = this->split_->getChannel();
|
||||
|
||||
if (auto twitchChannel = dynamic_cast<TwitchChannel *>(channel.get()))
|
||||
twitchChannel->refreshChannelEmotes();
|
||||
{
|
||||
twitchChannel->refreshFFZChannelEmotes();
|
||||
twitchChannel->refreshBTTVChannelEmotes();
|
||||
}
|
||||
}
|
||||
|
||||
void SplitHeader::reloadSubscriberEmotes()
|
||||
|
||||
Reference in New Issue
Block a user