Merge branch 'master' into git_is_pepega

This commit is contained in:
Mm2PL
2020-02-08 16:42:47 +01:00
committed by GitHub
100 changed files with 2424 additions and 5215 deletions
+2 -3
View File
@@ -11,7 +11,6 @@
#include "controllers/notifications/NotificationController.hpp"
#include "controllers/pings/PingController.hpp"
#include "controllers/taggedusers/TaggedUsersController.hpp"
#include "debug/Log.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/chatterino/ChatterinoBadges.hpp"
@@ -158,11 +157,11 @@ void Application::initNm(Paths &paths)
void Application::initPubsub()
{
this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) {
log("WHISPER SENT LOL"); //
qDebug() << "WHISPER SENT LOL"; //
});
this->twitch.pubsub->signals_.whisper.received.connect([](const auto &msg) {
log("WHISPER RECEIVED LOL"); //
qDebug() << "WHISPER RECEIVED LOL"; //
});
this->twitch.pubsub->signals_.moderation.chatCleared.connect(
-1
View File
@@ -1,7 +1,6 @@
#pragma once
#ifdef __cplusplus
# include <fmt/format.h>
# include <irccommand.h>
# include <ircconnection.h>
# include <rapidjson/document.h>
+1 -1
View File
@@ -1,9 +1,9 @@
#include "common/Channel.hpp"
#include "Application.hpp"
#include "debug/Log.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/IrcMessageHandler.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Logging.hpp"
#include "singletons/Settings.hpp"
-1
View File
@@ -6,7 +6,6 @@
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandController.hpp"
#include "debug/Benchmark.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
#include "singletons/Emotes.hpp"
+1 -2
View File
@@ -1,6 +1,5 @@
#include "DownloadManager.hpp"
#include "debug/Log.hpp"
#include "singletons/Paths.hpp"
#include <QDesktopServices>
@@ -41,7 +40,7 @@ void DownloadManager::setFile(QString fileURL, const QString &channelName)
void DownloadManager::onDownloadProgress(qint64 bytesRead, qint64 bytesTotal)
{
log("Download progress: {}/{}", bytesRead, bytesTotal);
qDebug() << "Download progress: " << bytesRead << "/" << bytesTotal;
}
void DownloadManager::onFinished(QNetworkReply *reply)
+1 -1
View File
@@ -61,7 +61,7 @@ Env::Env()
"https://i.nuuls.com/upload"))
, twitchServerHost(
readStringEnv("CHATTERINO2_TWITCH_SERVER_HOST", "irc.chat.twitch.tv"))
, twitchServerPort(readPortEnv("CHATTERINO2_TWITCH_SERVER_PORT", 6697))
, twitchServerPort(readPortEnv("CHATTERINO2_TWITCH_SERVER_PORT", 443))
, twitchServerSecure(readBoolEnv("CHATTERINO2_TWITCH_SERVER_SECURE", true))
{
}
+2 -3
View File
@@ -4,7 +4,6 @@
#include "common/NetworkResult.hpp"
#include "common/Outcome.hpp"
#include "debug/AssertInGuiThread.hpp"
#include "debug/Log.hpp"
#include "singletons/Paths.hpp"
#include "util/DebugCount.hpp"
#include "util/PostToThread.hpp"
@@ -124,7 +123,7 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
if (reply == nullptr)
{
log("Unhandled request type");
qDebug() << "Unhandled request type";
return;
}
@@ -132,7 +131,7 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
{
QObject::connect(
data->timer_, &QTimer::timeout, worker, [reply, data]() {
log("Aborted!");
qDebug() << "Aborted!";
reply->abort();
if (data->onError_)
{
-1
View File
@@ -4,7 +4,6 @@
#include "common/Outcome.hpp"
#include "common/Version.hpp"
#include "debug/AssertInGuiThread.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchCommon.hpp"
#include "singletons/Paths.hpp"
#include "util/DebugCount.hpp"
+3 -4
View File
@@ -1,7 +1,5 @@
#include "common/NetworkResult.hpp"
#include "debug/Log.hpp"
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <QJsonDocument>
@@ -45,8 +43,9 @@ rapidjson::Document NetworkResult::parseRapidJson() const
if (result.Code() != rapidjson::kParseErrorNone)
{
log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset());
qDebug() << "JSON parse error:"
<< rapidjson::GetParseError_En(result.Code()) << "("
<< result.Offset() << ")";
return ret;
}
+16 -1
View File
@@ -5,7 +5,6 @@
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/Command.hpp"
#include "controllers/commands/CommandModel.hpp"
#include "debug/Log.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "messages/MessageElement.hpp"
@@ -18,6 +17,7 @@
#include "singletons/Theme.hpp"
#include "util/CombinePath.hpp"
#include "widgets/dialogs/LogsPopup.hpp"
#include "widgets/dialogs/UserInfoPopup.hpp"
#include <QApplication>
#include <QFile>
@@ -482,6 +482,21 @@ QString CommandController::execCommand(const QString &textNoEmoji,
channelName + "/viewercard/" + words[1]);
return "";
}
else if (commandName == "/usercard")
{
if (words.size() < 2)
{
channel->addMessage(
makeSystemMessage("Usage /usercard [user]"));
return "";
}
auto *userPopup = new UserInfoPopup;
userPopup->setData(words[1], channel);
userPopup->setActionOnFocusLoss(BaseWindow::Delete);
userPopup->move(QCursor::pos());
userPopup->show();
return "";
}
}
{
@@ -18,16 +18,17 @@ HighlightBlacklistUser HighlightBlacklistModel::getItemFromRow(
{
// key, regex
return HighlightBlacklistUser{row[0]->data(Qt::DisplayRole).toString(),
row[1]->data(Qt::CheckStateRole).toBool()};
return HighlightBlacklistUser{
row[Column::Pattern]->data(Qt::DisplayRole).toString(),
row[Column::UseRegex]->data(Qt::CheckStateRole).toBool()};
}
// turns a row in the model into a vector item
void HighlightBlacklistModel::getRowFromItem(const HighlightBlacklistUser &item,
std::vector<QStandardItem *> &row)
{
setStringItem(row[0], item.getPattern());
setBoolItem(row[1], item.isRegex());
setStringItem(row[Column::Pattern], item.getPattern());
setBoolItem(row[Column::UseRegex], item.isRegex());
}
} // namespace chatterino
@@ -13,6 +13,12 @@ class HighlightBlacklistModel : public SignalVectorModel<HighlightBlacklistUser>
{
explicit HighlightBlacklistModel(QObject *parent);
public:
enum Column {
Pattern = 0,
UseRegex = 1,
};
protected:
// turn a vector item into a model row
virtual HighlightBlacklistUser getItemFromRow(
+140 -29
View File
@@ -8,7 +8,7 @@ namespace chatterino {
// commandmodel
HighlightModel::HighlightModel(QObject *parent)
: SignalVectorModel<HighlightPhrase>(5, parent)
: SignalVectorModel<HighlightPhrase>(7, parent)
{
}
@@ -16,52 +16,103 @@ HighlightModel::HighlightModel(QObject *parent)
HighlightPhrase HighlightModel::getItemFromRow(
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
{
// key, alert, sound, regex, case-sensitivity
// In order for old messages to update their highlight color, we need to
// update the highlight color here.
auto highlightColor = original.getColor();
*highlightColor =
row[Column::Color]->data(Qt::DecorationRole).value<QColor>();
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
row[1]->data(Qt::CheckStateRole).toBool(),
row[2]->data(Qt::CheckStateRole).toBool(),
row[3]->data(Qt::CheckStateRole).toBool(),
row[4]->data(Qt::CheckStateRole).toBool()};
return HighlightPhrase{
row[Column::Pattern]->data(Qt::DisplayRole).toString(),
row[Column::FlashTaskbar]->data(Qt::CheckStateRole).toBool(),
row[Column::PlaySound]->data(Qt::CheckStateRole).toBool(),
row[Column::UseRegex]->data(Qt::CheckStateRole).toBool(),
row[Column::CaseSensitive]->data(Qt::CheckStateRole).toBool(),
row[Column::SoundPath]->data(Qt::UserRole).toString(),
highlightColor};
}
// turns a row in the model into a vector item
void HighlightModel::getRowFromItem(const HighlightPhrase &item,
std::vector<QStandardItem *> &row)
{
setStringItem(row[0], item.getPattern());
setBoolItem(row[1], item.getAlert());
setBoolItem(row[2], item.getSound());
setBoolItem(row[3], item.isRegex());
setBoolItem(row[4], item.isCaseSensitive());
setStringItem(row[Column::Pattern], item.getPattern());
setBoolItem(row[Column::FlashTaskbar], item.hasAlert());
setBoolItem(row[Column::PlaySound], item.hasSound());
setBoolItem(row[Column::UseRegex], item.isRegex());
setBoolItem(row[Column::CaseSensitive], item.isCaseSensitive());
setFilePathItem(row[Column::SoundPath], item.getSoundUrl());
setColorItem(row[Column::Color], *item.getColor());
}
void HighlightModel::afterInit()
{
// Highlight settings for own username
std::vector<QStandardItem *> usernameRow = this->createRow();
setBoolItem(usernameRow[0], getSettings()->enableSelfHighlight.getValue(),
true, false);
usernameRow[0]->setData("Your username (automatic)", Qt::DisplayRole);
setBoolItem(usernameRow[1],
setBoolItem(usernameRow[Column::Pattern],
getSettings()->enableSelfHighlight.getValue(), true, false);
usernameRow[Column::Pattern]->setData("Your username (automatic)",
Qt::DisplayRole);
setBoolItem(usernameRow[Column::FlashTaskbar],
getSettings()->enableSelfHighlightTaskbar.getValue(), true,
false);
setBoolItem(usernameRow[2],
setBoolItem(usernameRow[Column::PlaySound],
getSettings()->enableSelfHighlightSound.getValue(), true,
false);
usernameRow[3]->setFlags(0);
usernameRow[Column::UseRegex]->setFlags(0);
usernameRow[Column::CaseSensitive]->setFlags(0);
QUrl selfSound = QUrl(getSettings()->selfHighlightSoundUrl.getValue());
setFilePathItem(usernameRow[Column::SoundPath], selfSound);
auto selfColor = ColorProvider::instance().color(ColorType::SelfHighlight);
setColorItem(usernameRow[Column::Color], *selfColor);
this->insertCustomRow(usernameRow, 0);
// Highlight settings for whispers
std::vector<QStandardItem *> whisperRow = this->createRow();
setBoolItem(whisperRow[0], getSettings()->enableWhisperHighlight.getValue(),
true, false);
whisperRow[0]->setData("Whispers", Qt::DisplayRole);
setBoolItem(whisperRow[1],
setBoolItem(whisperRow[Column::Pattern],
getSettings()->enableWhisperHighlight.getValue(), true, false);
whisperRow[Column::Pattern]->setData("Whispers", Qt::DisplayRole);
setBoolItem(whisperRow[Column::FlashTaskbar],
getSettings()->enableWhisperHighlightTaskbar.getValue(), true,
false);
setBoolItem(whisperRow[2],
setBoolItem(whisperRow[Column::PlaySound],
getSettings()->enableWhisperHighlightSound.getValue(), true,
false);
whisperRow[3]->setFlags(0);
whisperRow[Column::UseRegex]->setFlags(0);
whisperRow[Column::CaseSensitive]->setFlags(0);
QUrl whisperSound =
QUrl(getSettings()->whisperHighlightSoundUrl.getValue());
setFilePathItem(whisperRow[Column::SoundPath], whisperSound);
auto whisperColor = ColorProvider::instance().color(ColorType::Whisper);
setColorItem(whisperRow[Column::Color], *whisperColor);
this->insertCustomRow(whisperRow, 1);
// Highlight settings for subscription messages
std::vector<QStandardItem *> subRow = this->createRow();
setBoolItem(subRow[Column::Pattern],
getSettings()->enableSubHighlight.getValue(), true, false);
subRow[Column::Pattern]->setData("Subscriptions", Qt::DisplayRole);
setBoolItem(subRow[Column::FlashTaskbar],
getSettings()->enableSubHighlightTaskbar.getValue(), true,
false);
setBoolItem(subRow[Column::PlaySound],
getSettings()->enableSubHighlightSound.getValue(), true, false);
subRow[Column::UseRegex]->setFlags(0);
subRow[Column::CaseSensitive]->setFlags(0);
QUrl subSound = QUrl(getSettings()->subHighlightSoundUrl.getValue());
setFilePathItem(subRow[Column::SoundPath], subSound);
auto subColor = ColorProvider::instance().color(ColorType::Subscription);
setColorItem(subRow[Column::Color], *subColor);
this->insertCustomRow(subRow, 2);
}
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
@@ -70,7 +121,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
{
switch (column)
{
case 0: {
case Column::Pattern: {
if (role == Qt::CheckStateRole)
{
if (rowIndex == 0)
@@ -82,10 +133,14 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
getSettings()->enableWhisperHighlight.setValue(
value.toBool());
}
else if (rowIndex == 2)
{
getSettings()->enableSubHighlight.setValue(value.toBool());
}
}
}
break;
case 1: {
case Column::FlashTaskbar: {
if (role == Qt::CheckStateRole)
{
if (rowIndex == 0)
@@ -98,10 +153,15 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
getSettings()->enableWhisperHighlightTaskbar.setValue(
value.toBool());
}
else if (rowIndex == 2)
{
getSettings()->enableSubHighlightTaskbar.setValue(
value.toBool());
}
}
}
break;
case 2: {
case Column::PlaySound: {
if (role == Qt::CheckStateRole)
{
if (rowIndex == 0)
@@ -114,11 +174,62 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
getSettings()->enableWhisperHighlightSound.setValue(
value.toBool());
}
else if (rowIndex == 2)
{
getSettings()->enableSubHighlightSound.setValue(
value.toBool());
}
}
}
break;
case 3: {
// empty element
case Column::UseRegex: {
// Regex --> empty
}
break;
case Column::CaseSensitive: {
// Case-sensitivity --> empty
}
break;
case Column::SoundPath: {
// Custom sound file
if (role == Qt::UserRole)
{
if (rowIndex == 0)
{
getSettings()->selfHighlightSoundUrl.setValue(
value.toString());
}
else if (rowIndex == 1)
{
getSettings()->whisperHighlightSoundUrl.setValue(
value.toString());
}
else if (rowIndex == 2)
{
getSettings()->subHighlightSoundUrl.setValue(
value.toString());
}
}
}
break;
case Column::Color: {
// Custom color
if (role == Qt::DecorationRole)
{
auto colorName = value.value<QColor>().name(QColor::HexArgb);
if (rowIndex == 0)
{
getSettings()->selfHighlightColor.setValue(colorName);
}
else if (rowIndex == 1)
{
getSettings()->whisperHighlightColor.setValue(colorName);
}
else if (rowIndex == 2)
{
getSettings()->subHighlightColor.setValue(colorName);
}
}
}
break;
}
@@ -13,6 +13,18 @@ class HighlightModel : public SignalVectorModel<HighlightPhrase>
{
explicit HighlightModel(QObject *parent);
public:
// Used here, in HighlightingPage and in UserHighlightModel
enum Column {
Pattern = 0,
FlashTaskbar = 1,
PlaySound = 2,
UseRegex = 3,
CaseSensitive = 4,
SoundPath = 5,
Color = 6
};
protected:
// turn a vector item into a model row
virtual HighlightPhrase getItemFromRow(
@@ -0,0 +1,103 @@
#include "controllers/highlights/HighlightPhrase.hpp"
namespace chatterino {
bool HighlightPhrase::operator==(const HighlightPhrase &other) const
{
return std::tie(this->pattern_, this->hasSound_, this->hasAlert_,
this->isRegex_, this->isCaseSensitive_, this->soundUrl_,
this->color_) == std::tie(other.pattern_, other.hasSound_,
other.hasAlert_, other.isRegex_,
other.isCaseSensitive_,
other.soundUrl_, other.color_);
}
HighlightPhrase::HighlightPhrase(const QString &pattern, bool hasAlert,
bool hasSound, bool isRegex,
bool isCaseSensitive, const QString &soundUrl,
QColor color)
: pattern_(pattern)
, hasAlert_(hasAlert)
, hasSound_(hasSound)
, isRegex_(isRegex)
, isCaseSensitive_(isCaseSensitive)
, soundUrl_(soundUrl)
, regex_(isRegex_ ? pattern
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
QRegularExpression::UseUnicodePropertiesOption |
(isCaseSensitive_ ? QRegularExpression::NoPatternOption
: QRegularExpression::CaseInsensitiveOption))
{
this->color_ = std::make_shared<QColor>(color);
}
HighlightPhrase::HighlightPhrase(const QString &pattern, bool hasAlert,
bool hasSound, bool isRegex,
bool isCaseSensitive, const QString &soundUrl,
std::shared_ptr<QColor> color)
: pattern_(pattern)
, hasAlert_(hasAlert)
, hasSound_(hasSound)
, isRegex_(isRegex)
, isCaseSensitive_(isCaseSensitive)
, soundUrl_(soundUrl)
, color_(color)
, regex_(isRegex_ ? pattern
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
QRegularExpression::UseUnicodePropertiesOption |
(isCaseSensitive_ ? QRegularExpression::NoPatternOption
: QRegularExpression::CaseInsensitiveOption))
{
}
const QString &HighlightPhrase::getPattern() const
{
return this->pattern_;
}
bool HighlightPhrase::hasAlert() const
{
return this->hasAlert_;
}
bool HighlightPhrase::hasSound() const
{
return this->hasSound_;
}
bool HighlightPhrase::hasCustomSound() const
{
return !this->soundUrl_.isEmpty();
}
bool HighlightPhrase::isRegex() const
{
return this->isRegex_;
}
bool HighlightPhrase::isValid() const
{
return !this->pattern_.isEmpty() && this->regex_.isValid();
}
bool HighlightPhrase::isMatch(const QString &subject) const
{
return this->isValid() && this->regex_.match(subject).hasMatch();
}
bool HighlightPhrase::isCaseSensitive() const
{
return this->isCaseSensitive_;
}
const QUrl &HighlightPhrase::getSoundUrl() const
{
return this->soundUrl_;
}
const std::shared_ptr<QColor> HighlightPhrase::getColor() const
{
return this->color_;
}
} // namespace chatterino
+79 -64
View File
@@ -1,5 +1,6 @@
#pragma once
#include "providers/colors/ColorProvider.hpp"
#include "util/RapidJsonSerializeQString.hpp"
#include "util/RapidjsonHelpers.hpp"
@@ -12,70 +13,74 @@ namespace chatterino {
class HighlightPhrase
{
public:
bool operator==(const HighlightPhrase &other) const
{
return std::tie(this->pattern_, this->sound_, this->alert_,
this->isRegex_, this->caseSensitive_) ==
std::tie(other.pattern_, other.sound_, other.alert_,
other.isRegex_, other.caseSensitive_);
}
bool operator==(const HighlightPhrase &other) const;
HighlightPhrase(const QString &pattern, bool alert, bool sound,
bool isRegex, bool caseSensitive)
: pattern_(pattern)
, alert_(alert)
, sound_(sound)
, isRegex_(isRegex)
, caseSensitive_(caseSensitive)
, regex_(
isRegex_ ? pattern
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
QRegularExpression::UseUnicodePropertiesOption |
(caseSensitive_ ? QRegularExpression::NoPatternOption
: QRegularExpression::CaseInsensitiveOption))
{
}
/**
* @brief Create a new HighlightPhrase.
*
* Use this constructor when creating a new HighlightPhrase.
*/
HighlightPhrase(const QString &pattern, bool hasAlert, bool hasSound,
bool isRegex, bool isCaseSensitive, const QString &soundUrl,
QColor color);
const QString &getPattern() const
{
return this->pattern_;
}
bool getAlert() const
{
return this->alert_;
}
bool getSound() const
{
return this->sound_;
}
bool isRegex() const
{
return this->isRegex_;
}
/**
* @brief Create a new HighlightPhrase.
*
* Use this constructor when updating an existing HighlightPhrase's color.
*/
HighlightPhrase(const QString &pattern, bool hasAlert, bool hasSound,
bool isRegex, bool isCaseSensitive, const QString &soundUrl,
std::shared_ptr<QColor> color);
bool isValid() const
{
return !this->pattern_.isEmpty() && this->regex_.isValid();
}
const QString &getPattern() const;
bool hasAlert() const;
bool isMatch(const QString &subject) const
{
return this->isValid() && this->regex_.match(subject).hasMatch();
}
/**
* @brief Check if this highlight phrase should play a sound when
* triggered.
*
* In distinction from `HighlightPhrase::hasCustomSound`, this method only
* checks whether or not ANY sound should be played when the phrase is
* triggered.
*
* To check whether a custom sound is set, use
* `HighlightPhrase::hasCustomSound` instead.
*
* @return true, if this highlight phrase should play a sound when
* triggered, false otherwise
*/
bool hasSound() const;
bool isCaseSensitive() const
{
return this->caseSensitive_;
}
/**
* @brief Check if this highlight phrase has a custom sound set.
*
* Note that this method only checks whether the path to the custom sound
* is not empty. It does not check whether the file still exists, is a
* sound file, or anything else.
*
* @return true, if the custom sound file path is not empty, false otherwise
*/
bool hasCustomSound() const;
bool isRegex() const;
bool isValid() const;
bool isMatch(const QString &subject) const;
bool isCaseSensitive() const;
const QUrl &getSoundUrl() const;
const std::shared_ptr<QColor> getColor() const;
private:
QString pattern_;
bool alert_;
bool sound_;
bool hasAlert_;
bool hasSound_;
bool isRegex_;
bool caseSensitive_;
bool isCaseSensitive_;
QUrl soundUrl_;
std::shared_ptr<QColor> color_;
QRegularExpression regex_;
};
} // namespace chatterino
namespace pajlada {
@@ -88,10 +93,13 @@ struct Serialize<chatterino::HighlightPhrase> {
rapidjson::Value ret(rapidjson::kObjectType);
chatterino::rj::set(ret, "pattern", value.getPattern(), a);
chatterino::rj::set(ret, "alert", value.getAlert(), a);
chatterino::rj::set(ret, "sound", value.getSound(), a);
chatterino::rj::set(ret, "alert", value.hasAlert(), a);
chatterino::rj::set(ret, "sound", value.hasSound(), a);
chatterino::rj::set(ret, "regex", value.isRegex(), a);
chatterino::rj::set(ret, "case", value.isCaseSensitive(), a);
chatterino::rj::set(ret, "soundUrl", value.getSoundUrl().toString(), a);
chatterino::rj::set(ret, "color",
value.getColor()->name(QColor::HexArgb), a);
return ret;
}
@@ -104,23 +112,30 @@ struct Deserialize<chatterino::HighlightPhrase> {
if (!value.IsObject())
{
return chatterino::HighlightPhrase(QString(), true, false, false,
false);
false, "", QColor());
}
QString _pattern;
bool _alert = true;
bool _sound = false;
bool _hasAlert = true;
bool _hasSound = false;
bool _isRegex = false;
bool _caseSensitive = false;
bool _isCaseSensitive = false;
QString _soundUrl;
QString encodedColor;
chatterino::rj::getSafe(value, "pattern", _pattern);
chatterino::rj::getSafe(value, "alert", _alert);
chatterino::rj::getSafe(value, "sound", _sound);
chatterino::rj::getSafe(value, "alert", _hasAlert);
chatterino::rj::getSafe(value, "sound", _hasSound);
chatterino::rj::getSafe(value, "regex", _isRegex);
chatterino::rj::getSafe(value, "case", _caseSensitive);
chatterino::rj::getSafe(value, "case", _isCaseSensitive);
chatterino::rj::getSafe(value, "soundUrl", _soundUrl);
chatterino::rj::getSafe(value, "color", encodedColor);
return chatterino::HighlightPhrase(_pattern, _alert, _sound, _isRegex,
_caseSensitive);
auto _color = QColor(encodedColor);
return chatterino::HighlightPhrase(_pattern, _hasAlert, _hasSound,
_isRegex, _isCaseSensitive,
_soundUrl, _color);
}
};
@@ -1,6 +1,7 @@
#include "UserHighlightModel.hpp"
#include "Application.hpp"
#include "controllers/highlights/HighlightModel.hpp"
#include "singletons/Settings.hpp"
#include "util/StandardItemHelper.hpp"
@@ -8,7 +9,7 @@ namespace chatterino {
// commandmodel
UserHighlightModel::UserHighlightModel(QObject *parent)
: SignalVectorModel<HighlightPhrase>(5, parent)
: SignalVectorModel<HighlightPhrase>(7, parent)
{
}
@@ -16,24 +17,37 @@ UserHighlightModel::UserHighlightModel(QObject *parent)
HighlightPhrase UserHighlightModel::getItemFromRow(
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
{
// key, regex
using Column = HighlightModel::Column;
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
row[1]->data(Qt::CheckStateRole).toBool(),
row[2]->data(Qt::CheckStateRole).toBool(),
row[3]->data(Qt::CheckStateRole).toBool(),
row[4]->data(Qt::CheckStateRole).toBool()};
// In order for old messages to update their highlight color, we need to
// update the highlight color here.
auto highlightColor = original.getColor();
*highlightColor =
row[Column::Color]->data(Qt::DecorationRole).value<QColor>();
return HighlightPhrase{
row[Column::Pattern]->data(Qt::DisplayRole).toString(),
row[Column::FlashTaskbar]->data(Qt::CheckStateRole).toBool(),
row[Column::PlaySound]->data(Qt::CheckStateRole).toBool(),
row[Column::UseRegex]->data(Qt::CheckStateRole).toBool(),
row[Column::CaseSensitive]->data(Qt::CheckStateRole).toBool(),
row[Column::SoundPath]->data(Qt::UserRole).toString(),
highlightColor};
}
// row into vector item
void UserHighlightModel::getRowFromItem(const HighlightPhrase &item,
std::vector<QStandardItem *> &row)
{
setStringItem(row[0], item.getPattern());
setBoolItem(row[1], item.getAlert());
setBoolItem(row[2], item.getSound());
setBoolItem(row[3], item.isRegex());
setBoolItem(row[4], item.isCaseSensitive());
using Column = HighlightModel::Column;
setStringItem(row[Column::Pattern], item.getPattern());
setBoolItem(row[Column::FlashTaskbar], item.hasAlert());
setBoolItem(row[Column::PlaySound], item.hasSound());
setBoolItem(row[Column::UseRegex], item.isRegex());
setBoolItem(row[Column::CaseSensitive], item.isCaseSensitive());
setFilePathItem(row[Column::SoundPath], item.getSoundUrl());
setColorItem(row[Column::Color], *item.getColor());
}
} // namespace chatterino
@@ -4,7 +4,6 @@
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "controllers/notifications/NotificationModel.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchApi.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
#include "singletons/Toasts.hpp"
@@ -147,12 +146,13 @@ void NotificationController::getFakeTwitchChannelLiveStatus(
TwitchApi::findUserId(channelName, [channelName, this](QString roomID) {
if (roomID.isEmpty())
{
log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
channelName);
qDebug() << "[TwitchChannel" << channelName
<< "] Refreshing live status (Missing ID)";
removeFakeChannel(channelName);
return;
}
log("[TwitchChannel:{}] Refreshing live status", channelName);
qDebug() << "[TwitchChannel" << channelName
<< "] Refreshing live status";
QString url("https://api.twitch.tv/kraken/streams/" + roomID);
NetworkRequest::twitchRequest(url)
@@ -160,15 +160,15 @@ void NotificationController::getFakeTwitchChannelLiveStatus(
rapidjson::Document document = result.parseRapidJson();
if (!document.IsObject())
{
log("[TwitchChannel:refreshLiveStatus]root is not an "
"object");
qDebug() << "[TwitchChannel:refreshLiveStatus] root is not "
"an object";
return Failure;
}
if (!document.HasMember("stream"))
{
log("[TwitchChannel:refreshLiveStatus] Missing stream in "
"root");
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing "
"stream in root";
return Failure;
}
+2 -1
View File
@@ -10,7 +10,8 @@ BenchmarkGuard::BenchmarkGuard(const QString &_name)
BenchmarkGuard::~BenchmarkGuard()
{
log("{} {} ms", this->name_, float(timer_.nsecsElapsed()) / 1000000.0f);
qDebug() << this->name_ << float(timer_.nsecsElapsed()) / 1000000.0f
<< "ms";
}
qreal BenchmarkGuard::getElapsedMs()
+1 -2
View File
@@ -1,7 +1,6 @@
#pragma once
#include "debug/Log.hpp"
#include <QDebug>
#include <QElapsedTimer>
#include <boost/noncopyable.hpp>
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include "util/Helpers.hpp"
#include <QDebug>
#include <QTime>
namespace chatterino {
template <typename... Args>
inline void log(const std::string &formatString, Args &&... args)
{
qDebug().noquote() << QTime::currentTime().toString("hh:mm:ss.zzz")
<< fS(formatString, std::forward<Args>(args)...).c_str();
}
template <typename... Args>
inline void log(const char *formatString, Args &&... args)
{
log(std::string(formatString), std::forward<Args>(args)...);
}
template <typename... Args>
inline void log(const QString &formatString, Args &&... args)
{
log(formatString.toStdString(), std::forward<Args>(args)...);
}
} // namespace chatterino
+4 -5
View File
@@ -14,7 +14,6 @@
#include "common/NetworkRequest.hpp"
#include "debug/AssertInGuiThread.hpp"
#include "debug/Benchmark.hpp"
#include "debug/Log.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/WindowManager.hpp"
#include "util/DebugCount.hpp"
@@ -108,8 +107,8 @@ namespace detail {
if (reader.imageCount() == 0)
{
log("Error while reading image {}: '{}'", url.string,
reader.errorString());
qDebug() << "Error while reading image" << url.string << ": '"
<< reader.errorString() << "'";
return frames;
}
@@ -127,8 +126,8 @@ namespace detail {
if (frames.size() == 0)
{
log("Error while reading image {}: '{}'", url.string,
reader.errorString());
qDebug() << "Error while reading image" << url.string << ": '"
<< reader.errorString() << "'";
}
return frames;
+8 -3
View File
@@ -1,6 +1,9 @@
#include "messages/Message.hpp"
#include "Application.hpp"
#include "MessageElement.hpp"
#include "providers/twitch/PubsubActions.hpp"
#include "singletons/Theme.hpp"
#include "util/DebugCount.hpp"
#include "util/IrcHelpers.hpp"
@@ -24,11 +27,13 @@ SBHighlight Message::getScrollBarHighlight() const
if (this->flags.has(MessageFlag::Highlighted) ||
this->flags.has(MessageFlag::HighlightedWhisper))
{
return SBHighlight(SBHighlight::Highlight);
return SBHighlight(this->highlightColor);
}
else if (this->flags.has(MessageFlag::Subscription))
else if (this->flags.has(MessageFlag::Subscription) &&
getSettings()->enableSubHighlight)
{
return SBHighlight(SBHighlight::Subscription);
return SBHighlight(
ColorProvider::instance().color(ColorType::Subscription));
}
return SBHighlight();
}
+2
View File
@@ -33,6 +33,7 @@ enum class MessageFlag : uint32_t {
Whisper = (1 << 16),
HighlightedWhisper = (1 << 17),
Debug = (1 << 18),
Similar = (1 << 19),
};
using MessageFlags = FlagsEnum<MessageFlag>;
@@ -55,6 +56,7 @@ struct Message : boost::noncopyable {
QString displayName;
QString localizedName;
QString timeoutUser;
std::shared_ptr<QColor> highlightColor;
uint32_t count = 1;
std::vector<std::unique_ptr<MessageElement>> elements;
+40 -9
View File
@@ -25,6 +25,19 @@
namespace chatterino {
namespace {
QColor blendColors(const QColor &base, const QColor &apply)
{
const qreal &alpha = apply.alphaF();
QColor result;
result.setRgbF(base.redF() * (1 - alpha) + apply.redF() * alpha,
base.greenF() * (1 - alpha) + apply.greenF() * alpha,
base.blueF() * (1 - alpha) + apply.blueF() * alpha);
return result;
}
} // namespace
MessageLayout::MessageLayout(MessagePtr message)
: message_(message)
, container_(std::make_shared<MessageLayoutContainer>())
@@ -128,6 +141,12 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags)
continue;
}
if (getSettings()->hideSimilar &&
this->message_->flags.has(MessageFlag::Similar))
{
continue;
}
element->addToContainer(*this->container_, flags);
}
@@ -249,21 +268,33 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
painter.setRenderHint(QPainter::SmoothPixmapTransform);
// draw background
QColor backgroundColor = app->themes->messages.backgrounds.regular;
QColor backgroundColor = [this, &app] {
if (getSettings()->alternateMessages.getValue() &&
this->flags.has(MessageLayoutFlag::AlternateBackground))
{
return app->themes->messages.backgrounds.alternate;
}
else
{
return app->themes->messages.backgrounds.regular;
}
}();
if ((this->message_->flags.has(MessageFlag::Highlighted) ||
this->message_->flags.has(MessageFlag::HighlightedWhisper)) &&
!this->flags.has(MessageLayoutFlag::IgnoreHighlights))
{
backgroundColor = app->themes->messages.backgrounds.highlighted;
// Blend highlight color with usual background color
backgroundColor =
blendColors(backgroundColor, *this->message_->highlightColor);
}
else if (this->message_->flags.has(MessageFlag::Subscription))
else if (this->message_->flags.has(MessageFlag::Subscription) &&
getSettings()->enableSubHighlight)
{
backgroundColor = app->themes->messages.backgrounds.subscription;
}
else if (getSettings()->alternateMessages.getValue() &&
this->flags.has(MessageLayoutFlag::AlternateBackground))
{
backgroundColor = app->themes->messages.backgrounds.alternate;
// Blend highlight color with usual background color
backgroundColor = blendColors(
backgroundColor,
*ColorProvider::instance().color(ColorType::Subscription));
}
else if (this->message_->flags.has(MessageFlag::AutoMod))
{
-1
View File
@@ -5,7 +5,6 @@
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "messages/ImageSet.hpp"
+124
View File
@@ -0,0 +1,124 @@
#include "providers/colors/ColorProvider.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "singletons/Theme.hpp"
namespace chatterino {
const ColorProvider &ColorProvider::instance()
{
static ColorProvider instance;
return instance;
}
ColorProvider::ColorProvider()
: typeColorMap_()
, defaultColors_()
{
this->initTypeColorMap();
this->initDefaultColors();
}
const std::shared_ptr<QColor> ColorProvider::color(ColorType type) const
{
return this->typeColorMap_.at(type);
}
void ColorProvider::updateColor(ColorType type, QColor color)
{
auto colorPtr = this->typeColorMap_.at(type);
*colorPtr = color;
}
QSet<QColor> ColorProvider::recentColors() const
{
QSet<QColor> retVal;
/*
* Currently, only colors used in highlight phrases are considered. This
* may change at any point in the future.
*/
for (auto phrase : getApp()->highlights->phrases)
{
retVal.insert(*phrase.getColor());
}
for (auto userHl : getApp()->highlights->highlightedUsers)
{
retVal.insert(*userHl.getColor());
}
// Insert preset highlight colors
retVal.insert(*this->color(ColorType::SelfHighlight));
retVal.insert(*this->color(ColorType::Subscription));
retVal.insert(*this->color(ColorType::Whisper));
return retVal;
}
const std::vector<QColor> &ColorProvider::defaultColors() const
{
return this->defaultColors_;
}
void ColorProvider::initTypeColorMap()
{
// Read settings for custom highlight colors and save them in map.
// If no custom values can be found, set up default values instead.
auto backgrounds = getApp()->themes->messages.backgrounds;
QString customColor = getSettings()->selfHighlightColor;
if (QColor(customColor).isValid())
{
this->typeColorMap_.insert(
{ColorType::SelfHighlight, std::make_shared<QColor>(customColor)});
}
else
{
this->typeColorMap_.insert(
{ColorType::SelfHighlight,
std::make_shared<QColor>(backgrounds.highlighted)});
}
customColor = getSettings()->subHighlightColor;
if (QColor(customColor).isValid())
{
this->typeColorMap_.insert(
{ColorType::Subscription, std::make_shared<QColor>(customColor)});
}
else
{
this->typeColorMap_.insert(
{ColorType::Subscription,
std::make_shared<QColor>(backgrounds.subscription)});
}
customColor = getSettings()->whisperHighlightColor;
if (QColor(customColor).isValid())
{
this->typeColorMap_.insert(
{ColorType::Whisper, std::make_shared<QColor>(customColor)});
}
else
{
this->typeColorMap_.insert(
{ColorType::Whisper,
std::make_shared<QColor>(backgrounds.highlighted)});
}
}
void ColorProvider::initDefaultColors()
{
// Init default colors
this->defaultColors_.emplace_back(31, 141, 43, 127); // Green-ish
this->defaultColors_.emplace_back(28, 126, 141, 127); // Blue-ish
this->defaultColors_.emplace_back(136, 141, 49, 127); // Golden-ish
this->defaultColors_.emplace_back(143, 48, 24, 127); // Red-ish
this->defaultColors_.emplace_back(28, 141, 117, 127); // Cyan-ish
auto backgrounds = getApp()->themes->messages.backgrounds;
this->defaultColors_.push_back(backgrounds.highlighted);
this->defaultColors_.push_back(backgrounds.subscription);
}
} // namespace chatterino
+53
View File
@@ -0,0 +1,53 @@
#pragma once
namespace chatterino {
enum class ColorType { SelfHighlight, Subscription, Whisper };
class ColorProvider
{
public:
static const ColorProvider &instance();
/**
* @brief Return a std::shared_ptr to the color of the requested ColorType.
*
* If a custom color has been set for the requested ColorType, it is
* returned. If no custom color exists for the type, a default color is
* returned.
*
* We need to do this in order to be able to dynamically update the colors
* of already parsed predefined (self highlights, subscriptions,
* and whispers) highlights.
*/
const std::shared_ptr<QColor> color(ColorType type) const;
void updateColor(ColorType type, QColor color);
/**
* @brief Return a set of recently used colors used anywhere in Chatterino.
*/
QSet<QColor> recentColors() const;
/**
* @brief Return a vector of colors that are good defaults for use
* throughout the program.
*/
const std::vector<QColor> &defaultColors() const;
private:
ColorProvider();
void initTypeColorMap();
void initDefaultColors();
std::unordered_map<ColorType, std::shared_ptr<QColor>> typeColorMap_;
std::vector<QColor> defaultColors_;
};
} // namespace chatterino
// Adapted from Qt example: https://doc.qt.io/qt-5/qhash.html#qhash
inline uint qHash(const QColor &key)
{
return qHash(key.name(QColor::HexArgb));
}
+5 -5
View File
@@ -1,7 +1,6 @@
#include "providers/emoji/Emojis.hpp"
#include "Application.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "singletons/Settings.hpp"
@@ -132,8 +131,9 @@ void Emojis::loadEmojis()
if (result.Code() != rapidjson::kParseErrorNone)
{
log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset());
qDebug() << "JSON parse error:"
<< rapidjson::GetParseError_En(result.Code()) << "("
<< result.Offset() << ")";
return;
}
@@ -165,8 +165,8 @@ void Emojis::loadEmojis()
auto toneNameIt = toneNames.find(tone);
if (toneNameIt == toneNames.end())
{
log("Tone with key {} does not exist in tone names map",
tone);
qDebug() << "Tone with key" << tone.c_str()
<< "does not exist in tone names map";
continue;
}
+6 -6
View File
@@ -4,7 +4,6 @@
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
@@ -186,7 +185,8 @@ 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", channelId);
qDebug() << "[FFZEmotes] Reload FFZ Channel Emotes for channel"
<< channelId;
NetworkRequest("https://api.frankerfacez.com/v1/room/id/" + channelId)
@@ -211,13 +211,13 @@ void FfzEmotes::loadChannel(
else if (result.status() == NetworkResult::timedoutStatus)
{
// TODO: Auto retry in case of a timeout, with a delay
log("Fetching FFZ emotes for channel {} failed due to timeout",
channelId);
qDebug() << "Fetching FFZ emotes for channel" << channelId
<< "failed due to timeout";
}
else
{
log("Error fetching FFZ emotes for channel {}, error {}",
channelId, result.status());
qDebug() << "Error fetching FFZ emotes for channel" << channelId
<< ", error" << result.status();
}
})
.execute();
+16 -16
View File
@@ -2,7 +2,6 @@
#include "common/Channel.hpp"
#include "common/Common.hpp"
#include "debug/Log.hpp"
#include "messages/LimitedQueueSnapshot.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
@@ -66,7 +65,7 @@ AbstractIrcServer::AbstractIrcServer()
if (!this->readConnection_->isConnected())
{
log("Trying to reconnect... {}", this->falloffCounter_);
qDebug() << "Trying to reconnect..." << this->falloffCounter_;
this->connect();
}
});
@@ -159,23 +158,24 @@ ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName)
}
this->channels.insert(channelName, chan);
this->connections_.emplace_back(chan->destroyed.connect([this,
channelName] {
// fourtf: issues when the server itself is destroyed
this->connections_.emplace_back(
chan->destroyed.connect([this, channelName] {
// fourtf: issues when the server itself is destroyed
log("[AbstractIrcServer::addChannel] {} was destroyed", channelName);
this->channels.remove(channelName);
qDebug() << "[AbstractIrcServer::addChannel]" << channelName
<< "was destroyed";
this->channels.remove(channelName);
if (this->readConnection_)
{
this->readConnection_->sendRaw("PART #" + channelName);
}
if (this->readConnection_)
{
this->readConnection_->sendRaw("PART #" + channelName);
}
if (this->writeConnection_ && this->hasSeparateWriteConnection())
{
this->writeConnection_->sendRaw("PART #" + channelName);
}
}));
if (this->writeConnection_ && this->hasSeparateWriteConnection())
{
this->writeConnection_->sendRaw("PART #" + channelName);
}
}));
// join irc channel
{
+27 -1
View File
@@ -1,7 +1,15 @@
#include "IrcConnection2.hpp"
#include "common/Version.hpp"
namespace chatterino {
namespace {
const auto payload = QString("chatterino/" CHATTERINO_VERSION);
} // namespace
IrcConnection::IrcConnection(QObject *parent)
: Communi::IrcConnection(parent)
{
@@ -11,10 +19,17 @@ IrcConnection::IrcConnection(QObject *parent)
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
if (this->isConnected())
{
if (this->waitingForPong_.load())
{
// Not sending another ping as we haven't received the matching pong yet
return;
}
if (!this->recentlyReceivedMessage_.load())
{
this->sendRaw("PING chatterino/ping");
this->sendRaw("PING " + payload);
this->reconnectTimer_.start();
this->waitingForPong_ = true;
}
this->recentlyReceivedMessage_ = false;
}
@@ -30,6 +45,17 @@ IrcConnection::IrcConnection(QObject *parent)
}
});
QObject::connect(this, &Communi::IrcConnection::connected, this,
[this] { this->waitingForPong_ = false; });
QObject::connect(this, &Communi::IrcConnection::pongMessageReceived,
[this](Communi::IrcPongMessage *message) {
if (message->argument() == payload)
{
this->waitingForPong_ = false;
}
});
QObject::connect(this, &Communi::IrcConnection::messageReceived,
[this](Communi::IrcMessage *) {
this->recentlyReceivedMessage_ = true;
+3
View File
@@ -18,6 +18,9 @@ private:
QTimer pingTimer_;
QTimer reconnectTimer_;
std::atomic<bool> recentlyReceivedMessage_{true};
// waitingForPong_ is set to true when we send a PING message, and back to false when we receive the matching PONG response
std::atomic<bool> waitingForPong_{false};
};
} // namespace chatterino
+109 -14
View File
@@ -3,7 +3,6 @@
#include "Application.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "debug/Log.hpp"
#include "messages/LimitedQueue.hpp"
#include "messages/Message.hpp"
#include "providers/twitch/TwitchAccountManager.hpp"
@@ -22,6 +21,97 @@
namespace chatterino {
static float relativeSimilarity(const QString &str1, const QString &str2)
{
// Longest Common Substring Problem
std::vector<std::vector<int>> tree(str1.size(),
std::vector<int>(str2.size(), 0));
int z = 0;
for (int i = 0; i < str1.size(); ++i)
{
for (int j = 0; j < str2.size(); ++j)
{
if (str1[i] == str2[j])
{
if (i == 0 || j == 0)
{
tree[i][j] = 1;
}
else
{
tree[i][j] = tree[i - 1][j - 1] + 1;
}
if (tree[i][j] > z)
{
z = tree[i][j];
}
}
else
{
tree[i][j] = 0;
}
}
}
return z == 0 ? 0.f : float(z) / std::max(str1.size(), str2.size());
};
float IrcMessageHandler::similarity(
MessagePtr msg, const LimitedQueueSnapshot<MessagePtr> &messages)
{
float similarityPercent = 0.0f;
int bySameUser = 0;
for (int i = 1; bySameUser < getSettings()->hideSimilarMaxMessagesToCheck;
++i)
{
if (messages.size() < i)
{
break;
}
const auto &prevMsg = messages[messages.size() - i];
if (prevMsg->parseTime.secsTo(QTime::currentTime()) >=
getSettings()->hideSimilarMaxDelay)
{
break;
}
if (msg->loginName != prevMsg->loginName)
{
continue;
}
++bySameUser;
similarityPercent = std::max(
similarityPercent,
relativeSimilarity(msg->messageText, prevMsg->messageText));
}
return similarityPercent;
}
void IrcMessageHandler::setSimilarityFlags(MessagePtr msg, ChannelPtr chan)
{
if (getSettings()->similarityEnabled)
{
bool isMyself = msg->loginName ==
getApp()->accounts->twitch.getCurrent()->getUserName();
bool hideMyself = getSettings()->hideSimilarMyself;
if (isMyself && !hideMyself)
{
return;
}
if (IrcMessageHandler::similarity(msg, chan->getMessageSnapshot()) >
getSettings()->similarityPercentage)
{
msg->flags.set(MessageFlag::Similar, true);
if (getSettings()->colorSimilarDisabled)
{
msg->flags.set(MessageFlag::Disabled, true);
}
}
}
}
static QMap<QString, QString> parseBadges(QString badgesString)
{
QMap<QString, QString> badges;
@@ -134,7 +224,16 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
}
auto msg = builder.build();
builder.triggerHighlights();
IrcMessageHandler::setSimilarityFlags(msg, chan);
if (!msg->flags.has(MessageFlag::Similar) ||
(!getSettings()->hideSimilar &&
getSettings()->shownSimilarTriggerHighlights))
{
builder.triggerHighlights();
}
auto highlighted = msg->flags.has(MessageFlag::Highlighted);
if (!isSub)
@@ -235,9 +334,8 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
if (chan->isEmpty())
{
log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
"found",
chanName);
qDebug() << "[IrcMessageHandler:handleClearChatMessage] Twitch channel"
<< chanName << "not found";
return;
}
@@ -300,10 +398,9 @@ void IrcMessageHandler::handleClearMessageMessage(Communi::IrcMessage *message)
if (chan->isEmpty())
{
log("[IrcMessageHandler:handleClearMessageMessage] Twitch channel {} "
"not "
"found",
chanName);
qDebug()
<< "[IrcMessageHandler:handleClearMessageMessage] Twitch channel"
<< chanName << "not found";
return;
}
@@ -356,7 +453,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
{
auto app = getApp();
log("Received whisper!");
qDebug() << "Received whisper!";
MessageParseArgs args;
args.isReceivedWhisper = true;
@@ -568,10 +665,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
if (channel->isEmpty())
{
log("[IrcManager:handleNoticeMessage] Channel {} not found in "
"channel "
"manager ",
channelName);
qDebug() << "[IrcManager:handleNoticeMessage] Channel"
<< channelName << "not found in channel manager";
return;
}
@@ -1,6 +1,7 @@
#pragma once
#include <IrcMessage>
#include "common/Channel.hpp"
#include "messages/Message.hpp"
namespace chatterino {
@@ -49,6 +50,10 @@ public:
void handleJoinMessage(Communi::IrcMessage *message);
void handlePartMessage(Communi::IrcMessage *message);
static float similarity(MessagePtr msg,
const LimitedQueueSnapshot<MessagePtr> &messages);
static void setSimilarityFlags(MessagePtr message, ChannelPtr channel);
private:
void addMessage(Communi::IrcMessage *message, const QString &target,
const QString &content, TwitchIrcServer &server,
+20 -11
View File
@@ -2,7 +2,6 @@
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchCommon.hpp"
#include <QJsonArray>
@@ -28,6 +27,13 @@ PartialTwitchUser PartialTwitchUser::byId(const QString &id)
void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
const QObject *caller)
{
getId(
successCallback, [] {}, caller);
}
void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
std::function<void()> failureCallback,
const QObject *caller)
{
assert(!this->username_.isEmpty());
@@ -35,35 +41,38 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
this->username_)
.caller(caller)
.authorizeTwitchV5(getDefaultClientID())
.onSuccess([successCallback](auto result) -> Outcome {
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
auto root = result.parseJson();
if (!root.value("users").isArray())
{
log("API Error while getting user id, users is not an array");
qDebug()
<< "API Error while getting user id, users is not an array";
failureCallback();
return Failure;
}
auto users = root.value("users").toArray();
if (users.size() != 1)
{
log("API Error while getting user id, users array size is not "
"1");
qDebug() << "API Error while getting user id, users array size "
"is not 1";
failureCallback();
return Failure;
}
if (!users[0].isObject())
{
log("API Error while getting user id, first user is not an "
"object");
qDebug() << "API Error while getting user id, first user is "
"not an object";
failureCallback();
return Failure;
}
auto firstUser = users[0].toObject();
auto id = firstUser.value("_id");
if (!id.isString())
{
log("API Error: while getting user id, first user object `_id` "
"key "
"is not a "
"string");
qDebug() << "API Error: while getting user id, first user "
"object `_id` key is not a string";
failureCallback();
return Failure;
}
successCallback(id.toString());
@@ -21,6 +21,10 @@ public:
void getId(std::function<void(QString)> successCallback,
const QObject *caller = nullptr);
void getId(std::function<void(QString)> successCallback,
std::function<void()> failureCallback,
const QObject *caller = nullptr);
};
} // namespace chatterino
+69 -67
View File
@@ -1,8 +1,8 @@
#include "providers/twitch/PubsubClient.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/PubsubActions.hpp"
#include "providers/twitch/PubsubHelpers.hpp"
#include "util/Helpers.hpp"
#include "util/RapidjsonHelpers.hpp"
#include <rapidjson/error/en.h>
@@ -77,14 +77,14 @@ namespace detail {
return true;
}
void PubSubClient::unlistenPrefix(const std::string &prefix)
void PubSubClient::unlistenPrefix(const QString &prefix)
{
std::vector<std::string> topics;
std::vector<QString> topics;
for (auto it = this->listeners_.begin(); it != this->listeners_.end();)
{
const auto &listener = *it;
if (listener.topic.find(prefix) == 0)
if (listener.topic.startsWith(prefix))
{
topics.push_back(listener.topic);
it = this->listeners_.erase(it);
@@ -116,16 +116,14 @@ namespace detail {
{
assert(this->awaitingPong_);
log("Got pong!");
this->awaitingPong_ = false;
}
bool PubSubClient::isListeningToTopic(const std::string &payload)
bool PubSubClient::isListeningToTopic(const QString &topic)
{
for (const auto &listener : this->listeners_)
{
if (listener.topic == payload)
if (listener.topic == topic)
{
return true;
}
@@ -156,9 +154,8 @@ namespace detail {
if (self->awaitingPong_)
{
log("No pong response, disconnect!");
// TODO(pajlada): Label this connection as "disconnect
// me"
qDebug() << "No pong response, disconnect!";
// TODO(pajlada): Label this connection as "disconnect me"
}
});
@@ -181,7 +178,8 @@ namespace detail {
if (ec)
{
log("Error sending message {}: {}", payload, ec.message());
qDebug() << "Error sending message" << payload << ":"
<< ec.message().c_str();
// TODO(pajlada): Check which error code happened and maybe
// gracefully handle it
@@ -223,7 +221,7 @@ PubSub::PubSub()
if (!data.HasMember("args"))
{
log("Missing required args member");
qDebug() << "Missing required args member";
return;
}
@@ -231,13 +229,13 @@ PubSub::PubSub()
if (!args.IsArray())
{
log("args member must be an array");
qDebug() << "args member must be an array";
return;
}
if (args.Size() == 0)
{
log("Missing duration argument in slowmode on");
qDebug() << "Missing duration argument in slowmode on";
return;
}
@@ -245,7 +243,7 @@ PubSub::PubSub()
if (!durationArg.IsString())
{
log("Duration arg must be a string");
qDebug() << "Duration arg must be a string";
return;
}
@@ -338,7 +336,7 @@ PubSub::PubSub()
}
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what());
qDebug() << "Error parsing moderation action:" << ex.what();
}
action.modded = false;
@@ -368,7 +366,7 @@ PubSub::PubSub()
}
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what());
qDebug() << "Error parsing moderation action:" << ex.what();
}
action.modded = true;
@@ -417,7 +415,7 @@ PubSub::PubSub()
}
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what());
qDebug() << "Error parsing moderation action:" << ex.what();
}
};
@@ -454,7 +452,7 @@ PubSub::PubSub()
}
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what());
qDebug() << "Error parsing moderation action:" << ex.what();
}
};
@@ -485,7 +483,7 @@ PubSub::PubSub()
}
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what());
qDebug() << "Error parsing moderation action:" << ex.what();
}
};
@@ -516,7 +514,7 @@ PubSub::PubSub()
}
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what());
qDebug() << "Error parsing moderation action:" << ex.what();
}
};
@@ -568,7 +566,7 @@ PubSub::PubSub()
}
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what());
qDebug() << "Error parsing moderation action:" << ex.what();
}
};
@@ -597,7 +595,7 @@ PubSub::PubSub()
}
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what());
qDebug() << "Error parsing moderation action:" << ex.what();
}
};
@@ -626,7 +624,7 @@ PubSub::PubSub()
}
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what());
qDebug() << "Error parsing moderation action:" << ex.what();
}
};
@@ -655,7 +653,7 @@ PubSub::PubSub()
}
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what());
qDebug() << "Error parsing moderation action:" << ex.what();
}
};
@@ -685,7 +683,7 @@ PubSub::PubSub()
}
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what());
qDebug() << "Error parsing moderation action:" << ex.what();
}
};
@@ -738,7 +736,7 @@ void PubSub::addClient()
if (ec)
{
log("Unable to establish connection: {}", ec.message());
qDebug() << "Unable to establish connection:" << ec.message().c_str();
return;
}
@@ -753,20 +751,23 @@ void PubSub::start()
void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
{
static const QString topicFormat("whispers.%1");
assert(account != nullptr);
std::string userID = account->getUserId().toStdString();
auto userID = account->getUserId();
log("Connection open!");
qDebug() << "Connection open!";
websocketpp::lib::error_code ec;
std::vector<std::string> topics({"whispers." + userID});
std::vector<QString> topics({topicFormat.arg(userID)});
this->listen(createListenMessage(topics, account));
if (ec)
{
log("Unable to send message to websocket server: {}", ec.message());
qDebug() << "Unable to send message to websocket server:"
<< ec.message().c_str();
return;
}
}
@@ -783,26 +784,26 @@ void PubSub::unlistenAllModerationActions()
void PubSub::listenToChannelModerationActions(
const QString &channelID, std::shared_ptr<TwitchAccount> account)
{
static const QString topicFormat("chat_moderator_actions.%1.%2");
assert(!channelID.isEmpty());
assert(account != nullptr);
QString userID = account->getUserId();
if (userID.isEmpty())
return;
std::string topic(fS("chat_moderator_actions.{}.{}", userID, channelID));
auto topic = topicFormat.arg(userID).arg(channelID);
if (this->isListeningToTopic(topic))
{
log("We are already listening to topic {}", topic);
return;
}
log("Listen to topic {}", topic);
qDebug() << "Listen to topic" << topic;
this->listenToTopic(topic, account);
}
void PubSub::listenToTopic(const std::string &topic,
void PubSub::listenToTopic(const QString &topic,
std::shared_ptr<TwitchAccount> account)
{
auto message = createListenMessage({topic}, account);
@@ -814,20 +815,19 @@ void PubSub::listen(rapidjson::Document &&msg)
{
if (this->tryListen(msg))
{
log("Successfully listened!");
return;
}
this->addClient();
log("Added to the back of the queue");
qDebug() << "Added to the back of the queue";
this->requests.emplace_back(
std::make_unique<rapidjson::Document>(std::move(msg)));
}
bool PubSub::tryListen(rapidjson::Document &msg)
{
log("tryListen with {} clients", this->clients.size());
qDebug() << "tryListen with" << this->clients.size() << "clients";
for (const auto &p : this->clients)
{
const auto &client = p.second;
@@ -840,7 +840,7 @@ bool PubSub::tryListen(rapidjson::Document &msg)
return false;
}
bool PubSub::isListeningToTopic(const std::string &topic)
bool PubSub::isListeningToTopic(const QString &topic)
{
for (const auto &p : this->clients)
{
@@ -865,24 +865,24 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
if (!res)
{
log("Error parsing message '{}' from PubSub: {}", payload,
rapidjson::GetParseError_En(res.Code()));
qDebug() << "Error parsing message '" << payload.c_str()
<< "' from PubSub:" << rapidjson::GetParseError_En(res.Code());
return;
}
if (!msg.IsObject())
{
log("Error parsing message '{}' from PubSub. Root object is not an "
"object",
payload);
qDebug() << "Error parsing message '" << payload.c_str()
<< "' from PubSub. Root object is not an "
"object";
return;
}
std::string type;
QString type;
if (!rj::getSafe(msg, "type", type))
{
log("Missing required string member `type` in message root");
qDebug() << "Missing required string member `type` in message root";
return;
}
@@ -894,7 +894,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
{
if (!msg.HasMember("data"))
{
log("Missing required object member `data` in message root");
qDebug() << "Missing required object member `data` in message root";
return;
}
@@ -902,7 +902,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
if (!data.IsObject())
{
log("Member `data` must be an object");
qDebug() << "Member `data` must be an object";
return;
}
@@ -922,7 +922,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
}
else
{
log("Unknown message type: {}", type);
qDebug() << "Unknown message type:" << type;
}
}
@@ -983,7 +983,7 @@ PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
}
catch (const std::exception &e)
{
log("Exception caught in OnTLSInit: {}", e.what());
qDebug() << "Exception caught in OnTLSInit:" << e.what();
}
return ctx;
@@ -991,21 +991,21 @@ PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
void PubSub::handleListenResponse(const rapidjson::Document &msg)
{
std::string error;
QString error;
if (rj::getSafe(msg, "error", error))
{
std::string nonce;
QString nonce;
rj::getSafe(msg, "nonce", nonce);
if (error.empty())
if (error.isEmpty())
{
log("Successfully listened to nonce {}", nonce);
qDebug() << "Successfully listened to nonce" << nonce;
// Nothing went wrong
return;
}
log("PubSub error: {} on nonce {}", error, nonce);
qDebug() << "PubSub error:" << error << "on nonce" << nonce;
return;
}
}
@@ -1016,7 +1016,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
if (!rj::getSafe(outerData, "topic", topic))
{
log("Missing required string member `topic` in outerData");
qDebug() << "Missing required string member `topic` in outerData";
return;
}
@@ -1024,7 +1024,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
if (!rj::getSafe(outerData, "message", payload))
{
log("Expected string message in outerData");
qDebug() << "Expected string message in outerData";
return;
}
@@ -1034,8 +1034,8 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
if (!res)
{
log("Error parsing message '{}' from PubSub: {}", payload,
rapidjson::GetParseError_En(res.Code()));
qDebug() << "Error parsing message '" << payload.c_str()
<< "' from PubSub:" << rapidjson::GetParseError_En(res.Code());
return;
}
@@ -1045,7 +1045,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
if (!rj::getSafe(msg, "type", whisperType))
{
log("Bad whisper data");
qDebug() << "Bad whisper data";
return;
}
@@ -1063,7 +1063,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
}
else
{
log("Invalid whisper type: {}", whisperType);
qDebug() << "Invalid whisper type:" << whisperType.c_str();
assert(false);
return;
}
@@ -1078,7 +1078,8 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
if (!rj::getSafe(data, "moderation_action", moderationAction))
{
log("Missing moderation action in data: {}", rj::stringify(data));
qDebug() << "Missing moderation action in data:"
<< rj::stringify(data).c_str();
return;
}
@@ -1086,7 +1087,8 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
if (handlerIt == this->moderationActionHandlers.end())
{
log("No handler found for moderation action {}", moderationAction);
qDebug() << "No handler found for moderation action"
<< moderationAction.c_str();
return;
}
@@ -1095,16 +1097,16 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
}
else
{
log("Unknown topic: {}", topic);
qDebug() << "Unknown topic:" << topic;
return;
}
}
void PubSub::runThread()
{
log("Start pubsub manager thread");
qDebug() << "Start pubsub manager thread";
this->websocketClient.run();
log("Done with pubsub manager thread");
qDebug() << "Done with pubsub manager thread";
}
} // namespace chatterino
+5 -6
View File
@@ -15,7 +15,6 @@
#include <map>
#include <memory>
#include <set>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
@@ -33,7 +32,7 @@ using WebsocketErrorCode = websocketpp::lib::error_code;
namespace detail {
struct Listener {
std::string topic;
QString topic;
bool authed;
bool persistent;
bool confirmed = false;
@@ -49,11 +48,11 @@ namespace detail {
void stop();
bool listen(rapidjson::Document &message);
void unlistenPrefix(const std::string &prefix);
void unlistenPrefix(const QString &prefix);
void handlePong();
bool isListeningToTopic(const std::string &topic);
bool isListeningToTopic(const QString &topic);
private:
void ping();
@@ -135,13 +134,13 @@ public:
std::vector<std::unique_ptr<rapidjson::Document>> requests;
private:
void listenToTopic(const std::string &topic,
void listenToTopic(const QString &topic,
std::shared_ptr<TwitchAccount> account);
void listen(rapidjson::Document &&msg);
bool tryListen(rapidjson::Document &msg);
bool isListeningToTopic(const std::string &topic);
bool isListeningToTopic(const QString &topic);
void addClient();
+3 -5
View File
@@ -46,9 +46,8 @@ bool getTargetUser(const rapidjson::Value &data, ActionUser &user)
return rj::getSafe(data, "target_user_id", user.id);
}
rapidjson::Document createListenMessage(
const std::vector<std::string> &topicsVec,
std::shared_ptr<TwitchAccount> account)
rapidjson::Document createListenMessage(const std::vector<QString> &topicsVec,
std::shared_ptr<TwitchAccount> account)
{
rapidjson::Document msg(rapidjson::kObjectType);
auto &a = msg.GetAllocator();
@@ -75,8 +74,7 @@ rapidjson::Document createListenMessage(
return msg;
}
rapidjson::Document createUnlistenMessage(
const std::vector<std::string> &topicsVec)
rapidjson::Document createUnlistenMessage(const std::vector<QString> &topicsVec)
{
rapidjson::Document msg(rapidjson::kObjectType);
auto &a = msg.GetAllocator();
+5 -7
View File
@@ -3,7 +3,6 @@
#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
#include <memory>
#include "debug/Log.hpp"
#include "util/RapidjsonHelpers.hpp"
namespace chatterino {
@@ -18,11 +17,10 @@ bool getCreatedByUser(const rapidjson::Value &data, ActionUser &user);
bool getTargetUser(const rapidjson::Value &data, ActionUser &user);
rapidjson::Document createListenMessage(
const std::vector<std::string> &topicsVec,
std::shared_ptr<TwitchAccount> account);
rapidjson::Document createListenMessage(const std::vector<QString> &topicsVec,
std::shared_ptr<TwitchAccount> account);
rapidjson::Document createUnlistenMessage(
const std::vector<std::string> &topicsVec);
const std::vector<QString> &topicsVec);
// Create timer using given ioService
template <typename Duration, typename Callback>
@@ -35,7 +33,7 @@ void runAfter(boost::asio::io_service &ioService, Duration duration,
timer->async_wait([timer, cb](const boost::system::error_code &ec) {
if (ec)
{
log("Error in runAfter: {}", ec.message());
qDebug() << "Error in runAfter:" << ec.message().c_str();
return;
}
@@ -53,7 +51,7 @@ void runAfter(std::shared_ptr<boost::asio::steady_timer> timer,
timer->async_wait([timer, cb](const boost::system::error_code &ec) {
if (ec)
{
log("Error in runAfter: {}", ec.message());
qDebug() << "Error in runAfter:" << ec.message().c_str();
return;
}
+21 -15
View File
@@ -6,7 +6,6 @@
#include "common/Env.hpp"
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/PartialTwitchUser.hpp"
#include "providers/twitch/TwitchCommon.hpp"
#include "singletons/Emotes.hpp"
@@ -134,8 +133,8 @@ void TwitchAccount::loadIgnores()
TwitchUser ignoredUser;
if (!rj::getSafe(userIt->value, ignoredUser))
{
log("Error parsing twitch user JSON {}",
rj::stringify(userIt->value));
qDebug() << "Error parsing twitch user JSON"
<< rj::stringify(userIt->value).c_str();
continue;
}
@@ -345,14 +344,14 @@ std::set<TwitchUser> TwitchAccount::getIgnores() const
void TwitchAccount::loadEmotes()
{
log("Loading Twitch emotes for user {}", this->getUserName());
qDebug() << "Loading Twitch emotes for user" << this->getUserName();
const auto &clientID = this->getOAuthClient();
const auto &oauthToken = this->getOAuthToken();
if (clientID.isEmpty() || oauthToken.isEmpty())
{
log("Missing Client ID or OAuth token");
qDebug() << "Missing Client ID or OAuth token";
return;
}
@@ -363,7 +362,7 @@ void TwitchAccount::loadEmotes()
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](NetworkResult result) {
log("[TwitchAccount::loadEmotes] Error {}", result.status());
qDebug() << "[TwitchAccount::loadEmotes] Error" << result.status();
if (result.status() == 203)
{
// onFinished(FollowResult_NotFollowing);
@@ -402,7 +401,8 @@ void TwitchAccount::autoModAllow(const QString msgID)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](NetworkResult result) {
log("[TwitchAccounts::autoModAllow] Error {}", result.status());
qDebug() << "[TwitchAccounts::autoModAllow] Error"
<< result.status();
})
.execute();
}
@@ -421,7 +421,8 @@ void TwitchAccount::autoModDeny(const QString msgID)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](NetworkResult result) {
log("[TwitchAccounts::autoModDeny] Error {}", result.status());
qDebug() << "[TwitchAccounts::autoModDeny] Error"
<< result.status();
})
.execute();
}
@@ -436,7 +437,7 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
auto emoticonSets = root.FindMember("emoticon_sets");
if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject())
{
log("No emoticon_sets in load emotes response");
qDebug() << "No emoticon_sets in load emotes response";
return;
}
@@ -452,21 +453,21 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
{
if (!emoteJSON.IsObject())
{
log("Emote value was invalid");
qDebug() << "Emote value was invalid";
return;
}
uint64_t idNumber;
if (!rj::getSafe(emoteJSON, "id", idNumber))
{
log("No ID key found in Emote value");
qDebug() << "No ID key found in Emote value";
return;
}
QString _code;
if (!rj::getSafe(emoteJSON, "code", _code))
{
log("No code key found in Emote value");
qDebug() << "No code key found in Emote value";
return;
}
@@ -481,6 +482,10 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
emoteData->emotes.emplace(code, emote);
}
std::sort(emoteSet->emotes.begin(), emoteSet->emotes.end(),
[](const TwitchEmote &l, const TwitchEmote &r) {
return l.name.string < r.name.string;
});
emoteData->emoteSets.emplace_back(emoteSet);
}
};
@@ -489,7 +494,7 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
{
if (!emoteSet)
{
log("null emote set sent");
qDebug() << "null emote set sent";
return;
}
@@ -505,7 +510,8 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
NetworkRequest(Env::get().twitchEmoteSetResolverUrl.arg(emoteSet->key))
.cache()
.onError([](NetworkResult result) {
log("Error code {} while loading emote set data", result.status());
qDebug() << "Error code" << result.status()
<< "while loading emote set data";
})
.onSuccess([emoteSet](auto result) -> Outcome {
auto root = result.parseRapidJson();
@@ -527,7 +533,7 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
return Failure;
}
log("Loaded twitch emote set data for {}!", emoteSet->key);
qDebug() << "Loaded twitch emote set data for" << emoteSet->key;
auto name = channelName;
name.detach();
+11 -13
View File
@@ -1,7 +1,6 @@
#include "providers/twitch/TwitchAccountManager.hpp"
#include "common/Common.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchCommon.hpp"
@@ -103,23 +102,23 @@ void TwitchAccountManager::reloadUsers()
switch (this->addUser(userData))
{
case AddUserResponse::UserAlreadyExists: {
log("User {} already exists", userData.username);
qDebug() << "User" << userData.username << "already exists";
// Do nothing
}
break;
case AddUserResponse::UserValuesUpdated: {
log("User {} already exists, and values updated!",
userData.username);
qDebug() << "User" << userData.username
<< "already exists, and values updated!";
if (userData.username == this->getCurrent()->getUserName())
{
log("It was the current user, so we need to reconnect "
"stuff!");
qDebug() << "It was the current user, so we need to "
"reconnect stuff!";
this->currentUserChanged.invoke();
}
}
break;
case AddUserResponse::UserAdded: {
log("Added user {}", userData.username);
qDebug() << "Added user" << userData.username;
listUpdated = true;
}
break;
@@ -140,15 +139,12 @@ void TwitchAccountManager::load()
auto user = this->findUserByUsername(newUsername);
if (user)
{
log("[AccountManager:currentUsernameChanged] User successfully "
"updated to {}",
newUsername);
qDebug() << "Twitch user updated to" << newUsername;
this->currentUser_ = user;
}
else
{
log("[AccountManager:currentUsernameChanged] User successfully "
"updated to anonymous");
qDebug() << "Twitch user updated to anonymous";
this->currentUser_ = this->anonymousUser_;
}
@@ -170,11 +166,13 @@ bool TwitchAccountManager::isLoggedIn() const
bool TwitchAccountManager::removeUser(TwitchAccount *account)
{
static const QString accountFormat("/accounts/uid%1");
auto userID(account->getUserId());
if (!userID.isEmpty())
{
pajlada::Settings::SettingManager::removeSetting(
fS("/accounts/uid{}", userID));
accountFormat.arg(userID).toStdString());
}
if (account->getUserName() == this->currentUsername)
+10 -12
View File
@@ -2,7 +2,6 @@
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchCommon.hpp"
#include <QString>
@@ -23,22 +22,23 @@ void TwitchApi::findUserId(const QString user,
auto root = result.parseJson();
if (!root.value("users").isArray())
{
log("API Error while getting user id, users is not an array");
qDebug()
<< "API Error while getting user id, users is not an array";
successCallback("");
return Failure;
}
auto users = root.value("users").toArray();
if (users.size() != 1)
{
log("API Error while getting user id, users array size is not "
"1");
qDebug() << "API Error while getting user id, users array size "
"is not 1";
successCallback("");
return Failure;
}
if (!users[0].isObject())
{
log("API Error while getting user id, first user is not an "
"object");
qDebug() << "API Error while getting user id, first user is "
"not an object";
successCallback("");
return Failure;
}
@@ -46,10 +46,8 @@ void TwitchApi::findUserId(const QString user,
auto id = firstUser.value("_id");
if (!id.isString())
{
log("API Error: while getting user id, first user object `_id` "
"key "
"is not a "
"string");
qDebug() << "API Error: while getting user id, first user "
"object `_id` key is not a string";
successCallback("");
return Failure;
}
@@ -73,8 +71,8 @@ void TwitchApi::findUserName(const QString userid,
auto name = root.value("name");
if (!name.isString())
{
log("API Error: while getting user name, `name` is not a "
"string");
qDebug() << "API Error: while getting user name, `name` is not "
"a string";
successCallback("");
return Failure;
}
-1
View File
@@ -7,7 +7,6 @@
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
namespace chatterino {
+18 -11
View File
@@ -6,7 +6,6 @@
#include "common/NetworkRequest.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/notifications/NotificationController.hpp"
#include "debug/Log.hpp"
#include "messages/Message.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/bttv/LoadBttvChannelEmote.hpp"
@@ -92,7 +91,7 @@ TwitchChannel::TwitchChannel(const QString &name,
, mod_(false)
, titleRefreshedTime_(QTime::currentTime().addSecs(-TITLE_REFRESH_PERIOD))
{
log("[TwitchChannel:{}] Opened", name);
qDebug() << "[TwitchChannel" << name << "] Opened";
this->liveStatusChanged.connect([this]() {
if (this->isLive() == 1)
@@ -194,7 +193,8 @@ void TwitchChannel::sendMessage(const QString &message)
return;
}
log("[TwitchChannel:{}] Send message: {}", this->getName(), message);
qDebug() << "[TwitchChannel" << this->getName()
<< "] Send message:" << message;
// Do last message processing
QString parsedMessage = app->emotes->emojis.replaceShortCodes(message);
@@ -491,8 +491,8 @@ void TwitchChannel::refreshLiveStatus()
if (roomID.isEmpty())
{
log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
this->getName());
qDebug() << "[TwitchChannel" << this->getName()
<< "] Refreshing live status (Missing ID)";
this->setLive(false);
return;
}
@@ -517,13 +517,13 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
{
if (!document.IsObject())
{
log("[TwitchChannel:refreshLiveStatus] root is not an object");
qDebug() << "[TwitchChannel:refreshLiveStatus] root is not an object";
return Failure;
}
if (!document.HasMember("stream"))
{
log("[TwitchChannel:refreshLiveStatus] Missing stream in root");
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing stream in root";
return Failure;
}
@@ -539,7 +539,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
if (!stream.HasMember("viewers") || !stream.HasMember("game") ||
!stream.HasMember("channel") || !stream.HasMember("created_at"))
{
log("[TwitchChannel:refreshLiveStatus] Missing members in stream");
qDebug()
<< "[TwitchChannel:refreshLiveStatus] Missing members in stream";
this->setLive(false);
return Failure;
}
@@ -548,8 +549,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
if (!streamChannel.IsObject() || !streamChannel.HasMember("status"))
{
log("[TwitchChannel:refreshLiveStatus] Missing member \"status\" in "
"channel");
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing member "
"\"status\" in channel";
return Failure;
}
@@ -743,6 +744,12 @@ void TwitchChannel::refreshCheerEmotes()
NetworkRequest::twitchRequest(url)
.onSuccess([this,
weak = weakOf<Channel>(this)](auto result) -> Outcome {
auto shared = weak.lock();
if (!shared)
{
return Failure;
}
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
std::vector<CheerEmoteSet> emoteSets;
@@ -836,7 +843,7 @@ boost::optional<CheerEmote> TwitchChannel::cheerEmote(const QString &string)
int bitAmount = amount.toInt(&ok);
if (!ok)
{
log("Error parsing bit amount in cheerEmote");
qDebug() << "Error parsing bit amount in cheerEmote";
}
for (const auto &emote : set.cheerEmotes)
{
-1
View File
@@ -2,7 +2,6 @@
#include "common/NetworkRequest.hpp"
#include "debug/Benchmark.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "util/RapidjsonHelpers.hpp"
+1 -2
View File
@@ -1,5 +1,4 @@
#include "providers/twitch/TwitchHelpers.hpp"
#include "debug/Log.hpp"
namespace chatterino {
@@ -7,7 +6,7 @@ bool trimChannelName(const QString &channelName, QString &outChannelName)
{
if (channelName.length() < 2)
{
log("channel name length below 2");
qDebug() << "channel name length below 2";
return false;
}
+148 -63
View File
@@ -5,7 +5,6 @@
#include "controllers/highlights/HighlightController.hpp"
#include "controllers/ignores/IgnoreController.hpp"
#include "controllers/pings/PingController.hpp"
#include "debug/Log.hpp"
#include "messages/Message.hpp"
#include "providers/chatterino/ChatterinoBadges.hpp"
#include "providers/twitch/TwitchBadges.hpp"
@@ -65,6 +64,25 @@ QColor getRandomColor(const QVariant &userId)
return twitchUsernameColors[colorIndex];
}
QUrl getFallbackHighlightSound()
{
using namespace chatterino;
QString path = getSettings()->pathHighlightSound;
bool fileExists = QFileInfo::exists(path) && QFileInfo(path).isFile();
// Use fallback sound when checkbox is not checked
// or custom file doesn't exist
if (getSettings()->customHighlightSound && fileExists)
{
return QUrl::fromLocalFile(path);
}
else
{
return QUrl("qrc:/sounds/ping2.wav");
}
}
} // namespace
namespace chatterino {
@@ -90,8 +108,6 @@ namespace {
QStringList parts = badgeInfo.split('/');
if (parts.size() != 2)
{
log("Skipping badge-info because it split weird: {}",
badgeInfo);
continue;
}
@@ -110,7 +126,6 @@ namespace {
QStringList parts = badge.split('/');
if (parts.size() != 2)
{
log("Skipping badge because it split weird: {}", badge);
continue;
}
@@ -159,8 +174,8 @@ bool TwitchMessageBuilder::isIgnored() const
{
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
{
log("Blocking message because it contains ignored phrase {}",
phrase.getPattern());
qDebug() << "Blocking message because it contains ignored phrase"
<< phrase.getPattern();
return true;
}
}
@@ -190,8 +205,8 @@ bool TwitchMessageBuilder::isIgnored() const
case ShowIgnoredUsersMessages::Never:
break;
}
log("Blocking message because it's from blocked user {}",
user.name);
qDebug() << "Blocking message because it's from blocked user"
<< user.name;
return true;
}
}
@@ -237,17 +252,11 @@ void TwitchMessageBuilder::triggerHighlights()
if (auto player = getPlayer())
{
// update the media player url if necessary
QUrl highlightSoundUrl =
getSettings()->customHighlightSound
? QUrl::fromLocalFile(
getSettings()->pathHighlightSound.getValue())
: QUrl("qrc:/sounds/ping2.wav");
if (currentPlayerUrl != highlightSoundUrl)
if (currentPlayerUrl != this->highlightSoundUrl_)
{
player->setMedia(highlightSoundUrl);
player->setMedia(this->highlightSoundUrl_);
currentPlayerUrl = highlightSoundUrl;
currentPlayerUrl = this->highlightSoundUrl_;
}
player->play();
@@ -427,8 +436,8 @@ void TwitchMessageBuilder::addWords(
auto emoteImage = std::get<1>(*currentTwitchEmote);
if (emoteImage == nullptr)
{
log("emoteImage nullptr {}",
std::get<2>(*currentTwitchEmote).string);
qDebug() << "emoteImage nullptr"
<< std::get<2>(*currentTwitchEmote).string;
}
this->emplace<EmoteElement>(emoteImage,
MessageElementFlag::TwitchEmote);
@@ -771,7 +780,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
{
if (std::get<1>(*copy) == nullptr)
{
log("remem nullptr {}", std::get<2>(*copy).string);
qDebug() << "remem nullptr" << std::get<2>(*copy).string;
}
}
std::vector<std::tuple<int, EmotePtr, EmoteName>> v(
@@ -809,7 +818,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
{
if (emote.second == nullptr)
{
log("emote null {}", emote.first.string);
qDebug() << "emote null" << emote.first.string;
}
twitchEmotes.push_back(std::tuple<int, EmotePtr, EmoteName>{
startIndex + pos, emote.second, emote.first});
@@ -872,7 +881,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
{
if (std::get<1>(tup) == nullptr)
{
log("v nullptr {}", std::get<2>(tup).string);
qDebug() << "v nullptr" << std::get<2>(tup).string;
continue;
}
QRegularExpression emoteregex(
@@ -941,7 +950,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
{
if (std::get<1>(tup) == nullptr)
{
log("v nullptr {}", std::get<2>(tup).string);
qDebug() << "v nullptr" << std::get<2>(tup).string;
continue;
}
QRegularExpression emoteregex(
@@ -971,6 +980,39 @@ void TwitchMessageBuilder::parseHighlights()
{
auto app = getApp();
if (this->message().flags.has(MessageFlag::Subscription) &&
getSettings()->enableSubHighlight)
{
if (getSettings()->enableSubHighlightTaskbar)
{
this->highlightAlert_ = true;
}
if (getSettings()->enableSubHighlightSound)
{
this->highlightSound_ = true;
// Use custom sound if set, otherwise use fallback
if (!getSettings()->subHighlightSoundUrl.getValue().isEmpty())
{
this->highlightSoundUrl_ =
QUrl(getSettings()->subHighlightSoundUrl.getValue());
}
else
{
this->highlightSoundUrl_ = getFallbackHighlightSound();
}
}
this->message().flags.set(MessageFlag::Highlighted);
this->message().highlightColor =
ColorProvider::instance().color(ColorType::Subscription);
// This message was a subscription.
// Don't check for any other highlight phrases.
return;
}
auto currentUser = app->accounts->twitch.getCurrent();
QString currentUsername = currentUser->getUserName();
@@ -981,6 +1023,39 @@ void TwitchMessageBuilder::parseHighlights()
return;
}
// Highlight because it's a whisper
if (this->args.isReceivedWhisper && getSettings()->enableWhisperHighlight)
{
if (getSettings()->enableWhisperHighlightTaskbar)
{
this->highlightAlert_ = true;
}
if (getSettings()->enableWhisperHighlightSound)
{
this->highlightSound_ = true;
// Use custom sound if set, otherwise use fallback
if (!getSettings()->whisperHighlightSoundUrl.getValue().isEmpty())
{
this->highlightSoundUrl_ =
QUrl(getSettings()->whisperHighlightSoundUrl.getValue());
}
else
{
this->highlightSoundUrl_ = getFallbackHighlightSound();
}
}
this->message().highlightColor =
ColorProvider::instance().color(ColorType::Whisper);
/*
* Do _NOT_ return yet, we might want to apply phrase/user name
* highlights (which override whisper color/sound).
*/
}
std::vector<HighlightPhrase> userHighlights =
app->highlights->highlightedUsers.cloneVector();
@@ -991,29 +1066,39 @@ void TwitchMessageBuilder::parseHighlights()
{
continue;
}
log("Highlight because user {} sent a message",
this->ircMessage->nick());
if (!this->highlightVisual_)
{
this->highlightVisual_ = true;
this->message().flags.set(MessageFlag::Highlighted);
}
qDebug() << "Highlight because user" << this->ircMessage->nick()
<< "sent a message";
if (userHighlight.getAlert())
this->message().flags.set(MessageFlag::Highlighted);
this->message().highlightColor = userHighlight.getColor();
if (userHighlight.hasAlert())
{
this->highlightAlert_ = true;
}
if (userHighlight.getSound())
if (userHighlight.hasSound())
{
this->highlightSound_ = true;
// Use custom sound if set, otherwise use the fallback sound
if (userHighlight.hasCustomSound())
{
this->highlightSoundUrl_ = userHighlight.getSoundUrl();
}
else
{
this->highlightSoundUrl_ = getFallbackHighlightSound();
}
}
if (this->highlightAlert_ && this->highlightSound_)
{
// Break if no further action can be taken from other
// usernames Mostly used for regex stuff
break;
/*
* User name highlights "beat" highlight phrases: If a message has
* all attributes (color, taskbar flashing, sound) set, highlight
* phrases will not be checked.
*/
return;
}
}
@@ -1032,7 +1117,9 @@ void TwitchMessageBuilder::parseHighlights()
{
HighlightPhrase selfHighlight(
currentUsername, getSettings()->enableSelfHighlightTaskbar,
getSettings()->enableSelfHighlightSound, false, false);
getSettings()->enableSelfHighlightSound, false, false,
getSettings()->selfHighlightSoundUrl.getValue(),
ColorProvider::instance().color(ColorType::SelfHighlight));
activeHighlights.emplace_back(std::move(selfHighlight));
}
@@ -1044,45 +1131,43 @@ void TwitchMessageBuilder::parseHighlights()
continue;
}
log("Highlight because {} matches {}", this->originalMessage_,
highlight.getPattern());
if (!this->highlightVisual_)
{
this->highlightVisual_ = true;
this->message().flags.set(MessageFlag::Highlighted);
}
qDebug() << "Highlight because" << this->originalMessage_ << "matches"
<< highlight.getPattern();
if (highlight.getAlert())
this->message().flags.set(MessageFlag::Highlighted);
this->message().highlightColor = highlight.getColor();
if (highlight.hasAlert())
{
this->highlightAlert_ = true;
}
if (highlight.getSound())
// Only set highlightSound_ if it hasn't been set by username
// highlights already.
if (highlight.hasSound() && !this->highlightSound_)
{
this->highlightSound_ = true;
// Use custom sound if set, otherwise use fallback sound
if (highlight.hasCustomSound())
{
this->highlightSoundUrl_ = highlight.getSoundUrl();
}
else
{
this->highlightSoundUrl_ = getFallbackHighlightSound();
}
}
if (this->highlightAlert_ && this->highlightSound_)
{
// Break if no further action can be taken from other
// highlights This might change if highlights can have
// custom colors/sounds/actions
/*
* Break once no further attributes (taskbar, sound) can be
* applied.
*/
break;
}
}
// Highlight because it's a whisper
if (this->args.isReceivedWhisper && getSettings()->enableWhisperHighlight)
{
if (getSettings()->enableWhisperHighlightTaskbar)
{
this->highlightAlert_ = true;
}
if (getSettings()->enableWhisperHighlightSound)
{
this->highlightSound_ = true;
}
}
}
void TwitchMessageBuilder::appendTwitchEmote(
@@ -1130,7 +1215,7 @@ void TwitchMessageBuilder::appendTwitchEmote(
start, app->emotes->twitch.getOrCreateEmote(id, name), name};
if (std::get<1>(tup) == nullptr)
{
log("nullptr {}", std::get<2>(tup).string);
qDebug() << "nullptr" << std::get<2>(tup).string;
}
vec.push_back(std::move(tup));
}
@@ -1216,7 +1301,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
auto badgeEmote = this->getTwitchBadge(badge);
if (!badgeEmote)
{
log("No channel/global variant found {}", badge.key_);
qDebug() << "No channel/global variant found" << badge.key_;
continue;
}
auto tooltip = (*badgeEmote)->tooltip.string;
@@ -92,9 +92,10 @@ private:
const bool action_ = false;
bool highlightVisual_ = false;
bool highlightAlert_ = false;
bool highlightSound_ = false;
QUrl highlightSoundUrl_;
};
} // namespace chatterino
-1
View File
@@ -1,7 +1,6 @@
#include "singletons/Logging.hpp"
#include "Application.hpp"
#include "debug/Log.hpp"
#include "singletons/Paths.hpp"
#include "singletons/Settings.hpp"
-1
View File
@@ -1,7 +1,6 @@
#include "singletons/Settings.hpp"
#include "Application.hpp"
#include "debug/Log.hpp"
#include "singletons/Paths.hpp"
#include "singletons/Resources.hpp"
#include "singletons/WindowManager.hpp"
+36 -1
View File
@@ -146,18 +146,39 @@ public:
/// Highlighting
// BoolSetting enableHighlights = {"/highlighting/enabled", true};
BoolSetting customHighlightSound = {"/highlighting/useCustomSound", false};
BoolSetting enableSelfHighlight = {
"/highlighting/selfHighlight/nameIsHighlightKeyword", true};
BoolSetting enableSelfHighlightSound = {
"/highlighting/selfHighlight/enableSound", true};
BoolSetting enableSelfHighlightTaskbar = {
"/highlighting/selfHighlight/enableTaskbarFlashing", true};
QStringSetting selfHighlightSoundUrl = {
"/highlighting/selfHighlightSoundUrl", ""};
QStringSetting selfHighlightColor = {"/highlighting/selfHighlightColor",
""};
BoolSetting enableWhisperHighlight = {
"/highlighting/whisperHighlight/whispersHighlighted", true};
BoolSetting enableWhisperHighlightSound = {
"/highlighting/whisperHighlight/enableSound", false};
BoolSetting enableWhisperHighlightTaskbar = {
"/highlighting/whisperHighlight/enableTaskbarFlashing", false};
QStringSetting whisperHighlightSoundUrl = {
"/highlighting/whisperHighlightSoundUrl", ""};
QStringSetting whisperHighlightColor = {
"/highlighting/whisperHighlightColor", ""};
BoolSetting enableSubHighlight = {
"/highlighting/subHighlight/subsHighlighted", true};
BoolSetting enableSubHighlightSound = {
"/highlighting/subHighlight/enableSound", false};
BoolSetting enableSubHighlightTaskbar = {
"/highlighting/subHighlight/enableTaskbarFlashing", false};
QStringSetting subHighlightSoundUrl = {"/highlighting/subHighlightSoundUrl",
""};
QStringSetting subHighlightColor = {"/highlighting/subHighlightColor", ""};
QStringSetting highlightColor = {"/highlighting/color", ""};
BoolSetting longAlerts = {"/highlighting/alerts", false};
@@ -168,7 +189,7 @@ public:
QStringSetting logPath = {"/logging/path", ""};
QStringSetting pathHighlightSound = {"/highlighting/highlightSoundPath",
"qrc:/sounds/ping2.wav"};
""};
BoolSetting highlightAlwaysPlaySound = {"/highlighting/alwaysPlaySound",
false};
@@ -228,6 +249,20 @@ public:
IntSetting lastSelectChannelTab = {"/ui/lastSelectChannelTab", 0};
IntSetting lastSelectIrcConn = {"/ui/lastSelectIrcConn", 0};
// Similarity
BoolSetting similarityEnabled = {"/similarity/similarityEnabled", false};
BoolSetting colorSimilarDisabled = {"/similarity/colorSimilarDisabled",
true};
BoolSetting hideSimilar = {"/similarity/hideSimilar", false};
BoolSetting hideSimilarMyself = {"/similarity/hideSimilarMyself", false};
BoolSetting shownSimilarTriggerHighlights = {
"/similarity/shownSimilarTriggerHighlights", false};
FloatSetting similarityPercentage = {"/similarity/similarityPercentage",
0.9f};
IntSetting hideSimilarMaxDelay = {"/similarity/hideSimilarMaxDelay", 5};
IntSetting hideSimilarMaxMessagesToCheck = {
"/similarity/hideSimilarMaxMessagesToCheck", 3};
private:
void updateModerationActions();
};
+2 -6
View File
@@ -38,9 +38,6 @@ void Theme::actuallyUpdate(double hue, double multiplier)
this->splits.resizeHandle = QColor(0, 148, 255, 0xff);
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x50);
// Highlighted Messages: theme support quick-fix
this->messages.backgrounds.highlighted = QColor("#BD8489");
}
else
{
@@ -49,11 +46,10 @@ void Theme::actuallyUpdate(double hue, double multiplier)
this->splits.resizeHandle = QColor(0, 148, 255, 0x70);
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x20);
// Highlighted Messages: theme support quick-fix
this->messages.backgrounds.highlighted = QColor("#4B282C");
}
this->messages.backgrounds.highlighted = QColor(140, 84, 89, 127);
this->splits.header.background = getColor(0, sat, flat ? 1 : 0.9);
this->splits.header.border = getColor(0, sat, flat ? 1 : 0.85);
this->splits.header.text = this->messages.textColors.regular;
+2 -3
View File
@@ -13,7 +13,6 @@
#include "Application.hpp"
#include "debug/AssertInGuiThread.hpp"
#include "debug/Log.hpp"
#include "messages/MessageElement.hpp"
#include "providers/irc/Irc2.hpp"
#include "providers/irc/IrcChannel2.hpp"
@@ -264,7 +263,7 @@ Window *WindowManager::windowAt(int index)
{
return nullptr;
}
log("getting window at bad index {}", index);
qDebug() << "getting window at bad index" << index;
return this->windows_.at(index);
}
@@ -432,7 +431,7 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
void WindowManager::save()
{
log("[WindowManager] Saving");
qDebug() << "[WindowManager] Saving";
assertInGuiThread();
QJsonDocument document;
+2 -3
View File
@@ -1,7 +1,6 @@
#include "LoggingChannel.hpp"
#include "Application.hpp"
#include "debug/Log.hpp"
#include "singletons/Paths.hpp"
#include "singletons/Settings.hpp"
@@ -64,13 +63,13 @@ void LoggingChannel::openLogFile()
if (!QDir().mkpath(directory))
{
log("Unable to create logging path");
qDebug() << "Unable to create logging path";
return;
}
// Open file handle to log file of current date
QString fileName = directory + QDir::separator() + baseFileName;
log("Logging to {}", fileName);
qDebug() << "Logging to" << fileName;
this->fileHandle.setFileName(fileName);
this->fileHandle.open(QIODevice::Append);
+16
View File
@@ -0,0 +1,16 @@
#include "util/Clipboard.hpp"
#include <QApplication>
namespace chatterino {
void crossPlatformCopy(const QString &text)
{
auto clipboard = QApplication::clipboard();
clipboard->setText(text);
if (clipboard->supportsSelection())
{
clipboard->setText(text, QClipboard::Selection);
}
}
} // namespace chatterino
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <QString>
namespace chatterino {
void crossPlatformCopy(const QString &text);
} // namespace chatterino
-17
View File
@@ -1,16 +1,9 @@
#pragma once
#include <fmt/format.h>
#include <QString>
namespace chatterino {
template <typename... Args>
auto fS(Args &&... args)
{
return fmt::format(std::forward<Args>(args)...);
}
QString generateUuid();
QString formatRichLink(const QString &url, bool file = false);
@@ -21,13 +14,3 @@ QString formatRichNamedLink(const QString &url, const QString &name,
QString shortenString(const QString &str, unsigned maxWidth = 50);
} // namespace chatterino
namespace fmt {
// format_arg for QString
inline void format_arg(BasicFormatter<char> &f, const char *&, const QString &v)
{
f.writer().write("{}", v.toStdString());
}
} // namespace fmt
+1 -3
View File
@@ -5,8 +5,6 @@
#include <QSettings>
#include <QVariant>
#include "debug/Log.hpp"
namespace chatterino {
namespace {
#ifdef Q_OS_WIN
@@ -63,7 +61,7 @@ namespace {
if (command.isNull())
return QString();
log(command);
qDebug() << command;
// inject switch to enable private browsing
command = injectPrivateSwitch(command);
+13
View File
@@ -22,6 +22,19 @@ static void setStringItem(QStandardItem *item, const QString &value,
(editable ? (Qt::ItemIsEditable) : 0)));
}
static void setFilePathItem(QStandardItem *item, const QUrl &value)
{
item->setData(value, Qt::UserRole);
item->setData(value.fileName(), Qt::DisplayRole);
item->setFlags(Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable));
}
static void setColorItem(QStandardItem *item, const QColor &value)
{
item->setData(value, Qt::DecorationRole);
item->setFlags(Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable));
}
static QStandardItem *emptyItem()
{
auto *item = new QStandardItem();
+2 -3
View File
@@ -1,7 +1,6 @@
#include "util/StreamLink.hpp"
#include "Application.hpp"
#include "debug/Log.hpp"
#include "singletons/Settings.hpp"
#include "util/Helpers.hpp"
#include "widgets/dialogs/QualityPopup.hpp"
@@ -92,7 +91,7 @@ namespace {
}
else
{
log("Error occured {}", err);
qDebug() << "Error occured" << err;
}
p->deleteLater();
@@ -119,7 +118,7 @@ void getStreamQualities(const QString &channelURL,
[=](int res) {
if (res != 0)
{
log("Got error code {}", res);
qDebug() << "Got error code" << res;
// return;
}
QString lastLine = QString(p->readAllStandardOutput());
-1
View File
@@ -1,5 +1,4 @@
#include "widgets/AccountSwitchPopup.hpp"
#include "debug/Log.hpp"
#include "widgets/dialogs/SettingsDialog.hpp"
#include <QHBoxLayout>
-1
View File
@@ -2,7 +2,6 @@
#include "BaseSettings.hpp"
#include "BaseTheme.hpp"
#include "debug/Log.hpp"
#include "widgets/BaseWindow.hpp"
#include <QChildEvent>
+2 -3
View File
@@ -3,7 +3,6 @@
#include "BaseSettings.hpp"
#include "BaseTheme.hpp"
#include "boost/algorithm/algorithm.hpp"
#include "debug/Log.hpp"
#include "util/PostToThread.hpp"
#include "util/Shortcut.hpp"
#include "util/WindowsHelper.hpp"
@@ -385,7 +384,7 @@ void BaseWindow::mousePressEvent(QMouseEvent *event)
if (!recursiveCheckMouseTracking(widget))
{
log("Start moving");
qDebug() << "Start moving";
this->moving = true;
}
}
@@ -402,7 +401,7 @@ void BaseWindow::mouseReleaseEvent(QMouseEvent *event)
{
if (this->moving)
{
log("Stop moving");
qDebug() << "Stop moving";
this->moving = false;
}
}
-1
View File
@@ -1,7 +1,6 @@
#include "widgets/Notebook.hpp"
#include "Application.hpp"
#include "debug/Log.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp"
+1 -12
View File
@@ -282,18 +282,7 @@ void Scrollbar::paintEvent(QPaintEvent *)
if (!highlight.isNull())
{
QColor color = [&] {
switch (highlight.getColor())
{
case ScrollbarHighlight::Highlight:
return getApp()
->themes->scrollbars.highlights.highlight;
case ScrollbarHighlight::Subscription:
return getApp()
->themes->scrollbars.highlights.subscription;
}
return QColor();
}();
QColor color = highlight.getColor();
switch (highlight.getStyle())
{
+5
View File
@@ -351,6 +351,11 @@ void Window::addShortcuts()
getApp()->twitch.server->getOrAddChannel(si.channelName));
splitContainer->appendSplit(split);
});
createWindowShortcut(this, "CTRL+H", [this] {
getSettings()->hideSimilar.setValue(!getSettings()->hideSimilar);
getApp()->windows->forceLayoutChannelViews();
});
}
void Window::addMenuBar()
+367
View File
@@ -0,0 +1,367 @@
#include "widgets/dialogs/ColorPickerDialog.hpp"
#include "providers/colors/ColorProvider.hpp"
#include "singletons/Theme.hpp"
namespace chatterino {
ColorPickerDialog::ColorPickerDialog(const QColor &initial, QWidget *parent)
: BasePopup(BaseWindow::EnableCustomFrame, parent)
, color_()
, dialogConfirmed_(false)
{
// This hosts the "business logic" and the dialog button box
LayoutCreator<QWidget> layoutWidget(this->getLayoutContainer());
auto layout = layoutWidget.setLayoutType<QVBoxLayout>().withoutMargin();
// This hosts the business logic: color picker and predefined colors
LayoutCreator<QWidget> contentCreator(new QWidget());
auto contents = contentCreator.setLayoutType<QHBoxLayout>();
// This hosts the predefined colors (and also the currently selected color)
LayoutCreator<QWidget> predefCreator(new QWidget());
auto predef = predefCreator.setLayoutType<QVBoxLayout>();
// Recently used colors
{
LayoutCreator<QWidget> gridCreator(new QWidget());
this->initRecentColors(gridCreator);
predef.append(gridCreator.getElement());
}
// Default colors
{
LayoutCreator<QWidget> gridCreator(new QWidget());
this->initDefaultColors(gridCreator);
predef.append(gridCreator.getElement());
}
// Currently selected color
{
LayoutCreator<QWidget> curColorCreator(new QWidget());
auto curColor = curColorCreator.setLayoutType<QHBoxLayout>();
curColor.emplace<QLabel>("Selected:").assign(&this->ui_.selected.label);
curColor.emplace<ColorButton>(initial).assign(
&this->ui_.selected.color);
predef.append(curColor.getElement());
}
contents.append(predef.getElement());
// Color picker
{
LayoutCreator<QWidget> obj(new QWidget());
auto vbox = obj.setLayoutType<QVBoxLayout>();
// The actual color picker
{
LayoutCreator<QWidget> cpCreator(new QWidget());
this->initColorPicker(cpCreator);
vbox.append(cpCreator.getElement());
}
// Spin boxes
{
LayoutCreator<QWidget> sbCreator(new QWidget());
this->initSpinBoxes(sbCreator);
vbox.append(sbCreator.getElement());
}
// HTML color
{
LayoutCreator<QWidget> htmlCreator(new QWidget());
this->initHtmlColor(htmlCreator);
vbox.append(htmlCreator.getElement());
}
contents.append(obj.getElement());
}
layout.append(contents.getElement());
// Dialog buttons
auto buttons =
layout.emplace<QHBoxLayout>().emplace<QDialogButtonBox>(this);
{
auto *button_ok = buttons->addButton(QDialogButtonBox::Ok);
QObject::connect(button_ok, &QPushButton::clicked,
[=](bool) { this->ok(); });
auto *button_cancel = buttons->addButton(QDialogButtonBox::Cancel);
QObject::connect(button_cancel, &QAbstractButton::clicked,
[=](bool) { this->close(); });
}
this->themeChangedEvent();
this->selectColor(initial, false);
}
ColorPickerDialog::~ColorPickerDialog()
{
if (this->htmlColorValidator_)
{
this->htmlColorValidator_->deleteLater();
this->htmlColorValidator_ = nullptr;
}
}
QColor ColorPickerDialog::selectedColor() const
{
if (!this->dialogConfirmed_)
{
// If the Cancel button was clicked, return the invalid color
return QColor();
}
return this->color_;
}
void ColorPickerDialog::closeEvent(QCloseEvent *)
{
this->closed.invoke();
}
void ColorPickerDialog::themeChangedEvent()
{
BaseWindow::themeChangedEvent();
QString textCol = this->theme->splits.input.text.name(QColor::HexRgb);
QString bgCol = this->theme->splits.input.background.name(QColor::HexRgb);
// Labels
QString labelStyle = QString("color: %1;").arg(textCol);
this->ui_.recent.label->setStyleSheet(labelStyle);
this->ui_.def.label->setStyleSheet(labelStyle);
this->ui_.selected.label->setStyleSheet(labelStyle);
this->ui_.picker.htmlLabel->setStyleSheet(labelStyle);
for (auto spinBoxLabel : this->ui_.picker.spinBoxLabels)
{
spinBoxLabel->setStyleSheet(labelStyle);
}
this->ui_.picker.htmlEdit->setStyleSheet(
this->theme->splits.input.styleSheet);
// Styling spin boxes is too much effort
}
void ColorPickerDialog::selectColor(const QColor &color, bool fromColorPicker)
{
if (color == this->color_)
return;
this->color_ = color;
// Update UI elements
this->ui_.selected.color->setColor(this->color_);
/*
* Somewhat "ugly" hack to prevent feedback loop between widgets. Since
* this method is private, I'm okay with this being ugly.
*/
if (!fromColorPicker)
{
this->ui_.picker.colorPicker->setCol(this->color_.hslHue(),
this->color_.hslSaturation());
this->ui_.picker.luminancePicker->setCol(this->color_.hsvHue(),
this->color_.hsvSaturation(),
this->color_.value());
}
this->ui_.picker.spinBoxes[SpinBox::RED]->setValue(this->color_.red());
this->ui_.picker.spinBoxes[SpinBox::GREEN]->setValue(this->color_.green());
this->ui_.picker.spinBoxes[SpinBox::BLUE]->setValue(this->color_.blue());
this->ui_.picker.spinBoxes[SpinBox::ALPHA]->setValue(this->color_.alpha());
/*
* Here, we are intentionally using HexRgb instead of HexArgb. Most online
* sites (or other applications) will likely not include the alpha channel
* in their output.
*/
this->ui_.picker.htmlEdit->setText(this->color_.name(QColor::HexRgb));
}
void ColorPickerDialog::ok()
{
this->dialogConfirmed_ = true;
this->close();
}
void ColorPickerDialog::initRecentColors(LayoutCreator<QWidget> &creator)
{
auto grid = creator.setLayoutType<QGridLayout>();
auto label = this->ui_.recent.label = new QLabel("Recently used:");
grid->addWidget(label, 0, 0, 1, -1);
const auto recentColors = ColorProvider::instance().recentColors();
auto it = recentColors.begin();
size_t ind = 0;
while (it != recentColors.end() && ind < MAX_RECENT_COLORS)
{
this->ui_.recent.colors.push_back(new ColorButton(*it, this));
auto *button = this->ui_.recent.colors[ind];
const int rowInd = (ind / RECENT_COLORS_PER_ROW) + 1;
const int columnInd = ind % RECENT_COLORS_PER_ROW;
grid->addWidget(button, rowInd, columnInd);
QObject::connect(button, &QPushButton::clicked,
[=] { this->selectColor(button->color(), false); });
++it;
++ind;
}
auto spacer =
new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
grid->addItem(spacer, (ind / RECENT_COLORS_PER_ROW) + 2, 0, 1, 1,
Qt::AlignTop);
}
void ColorPickerDialog::initDefaultColors(LayoutCreator<QWidget> &creator)
{
auto grid = creator.setLayoutType<QGridLayout>();
auto label = this->ui_.def.label = new QLabel("Default colors:");
grid->addWidget(label, 0, 0, 1, -1);
const auto defaultColors = ColorProvider::instance().defaultColors();
auto it = defaultColors.begin();
size_t ind = 0;
while (it != defaultColors.end())
{
this->ui_.def.colors.push_back(new ColorButton(*it, this));
auto *button = this->ui_.def.colors[ind];
const int rowInd = (ind / DEFAULT_COLORS_PER_ROW) + 1;
const int columnInd = ind % DEFAULT_COLORS_PER_ROW;
grid->addWidget(button, rowInd, columnInd);
QObject::connect(button, &QPushButton::clicked,
[=] { this->selectColor(button->color(), false); });
++it;
++ind;
}
auto spacer =
new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
grid->addItem(spacer, (ind / DEFAULT_COLORS_PER_ROW) + 2, 0, 1, 1,
Qt::AlignTop);
}
void ColorPickerDialog::initColorPicker(LayoutCreator<QWidget> &creator)
{
auto cpPanel = creator.setLayoutType<QHBoxLayout>();
/*
* For some reason, LayoutCreator::emplace didn't work for these.
* (Or maybe I was too dense to make it work.)
* After trying to debug for 4 hours or so, I gave up and settled
* for this solution.
*/
auto *colorPicker = new QColorPicker(this);
this->ui_.picker.colorPicker = colorPicker;
auto *luminancePicker = new QColorLuminancePicker(this);
this->ui_.picker.luminancePicker = luminancePicker;
cpPanel.append(colorPicker);
cpPanel.append(luminancePicker);
QObject::connect(colorPicker, SIGNAL(newCol(int, int)), luminancePicker,
SLOT(setCol(int, int)));
QObject::connect(
luminancePicker, &QColorLuminancePicker::newHsv,
[=](int h, int s, int v) {
int alpha = this->ui_.picker.spinBoxes[SpinBox::ALPHA]->value();
this->selectColor(QColor::fromHsv(h, s, v, alpha), true);
});
}
void ColorPickerDialog::initSpinBoxes(LayoutCreator<QWidget> &creator)
{
auto spinBoxes = creator.setLayoutType<QGridLayout>();
auto *red = this->ui_.picker.spinBoxes[SpinBox::RED] =
new QColSpinBox(this);
auto *green = this->ui_.picker.spinBoxes[SpinBox::GREEN] =
new QColSpinBox(this);
auto *blue = this->ui_.picker.spinBoxes[SpinBox::BLUE] =
new QColSpinBox(this);
auto *alpha = this->ui_.picker.spinBoxes[SpinBox::ALPHA] =
new QColSpinBox(this);
// We need pointers to these for theme changes
auto *redLbl = this->ui_.picker.spinBoxLabels[SpinBox::RED] =
new QLabel("Red:");
auto *greenLbl = this->ui_.picker.spinBoxLabels[SpinBox::GREEN] =
new QLabel("Green:");
auto *blueLbl = this->ui_.picker.spinBoxLabels[SpinBox::BLUE] =
new QLabel("Blue:");
auto *alphaLbl = this->ui_.picker.spinBoxLabels[SpinBox::ALPHA] =
new QLabel("Alpha:");
spinBoxes->addWidget(redLbl, 0, 0);
spinBoxes->addWidget(red, 0, 1);
spinBoxes->addWidget(greenLbl, 1, 0);
spinBoxes->addWidget(green, 1, 1);
spinBoxes->addWidget(blueLbl, 2, 0);
spinBoxes->addWidget(blue, 2, 1);
spinBoxes->addWidget(alphaLbl, 3, 0);
spinBoxes->addWidget(alpha, 3, 1);
for (size_t i = 0; i < SpinBox::END; ++i)
{
QObject::connect(
this->ui_.picker.spinBoxes[i],
QOverload<int>::of(&QSpinBox::valueChanged), [=](int value) {
this->selectColor(QColor(red->value(), green->value(),
blue->value(), alpha->value()),
false);
});
}
}
void ColorPickerDialog::initHtmlColor(LayoutCreator<QWidget> &creator)
{
auto html = creator.setLayoutType<QGridLayout>();
// Copied from Qt source for QColorShower
static QRegularExpression regExp(
QStringLiteral("#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})"));
auto *validator = this->htmlColorValidator_ =
new QRegularExpressionValidator(regExp, this);
auto *htmlLabel = this->ui_.picker.htmlLabel = new QLabel("HTML:");
auto *htmlEdit = this->ui_.picker.htmlEdit = new QLineEdit(this);
htmlEdit->setValidator(validator);
html->addWidget(htmlLabel, 0, 0);
html->addWidget(htmlEdit, 0, 1);
QObject::connect(htmlEdit, &QLineEdit::textEdited,
[=](const QString &text) {
QColor col(text);
if (col.isValid())
this->selectColor(col, false);
});
}
} // namespace chatterino
+110
View File
@@ -0,0 +1,110 @@
#pragma once
#include "util/LayoutCreator.hpp"
#include "widgets/BasePopup.hpp"
#include "widgets/helper/ColorButton.hpp"
#include "widgets/helper/QColorPicker.hpp"
#include <pajlada/signals/signal.hpp>
#include <array>
namespace chatterino {
/**
* @brief A custom color picker dialog.
*
* This class exists because QColorPickerDialog did not suit our use case.
* This dialog provides buttons for recently used and default colors, as well
* as a color picker widget identical to the one used in QColorPickerDialog.
*/
class ColorPickerDialog : public BasePopup
{
public:
/**
* @brief Create a new color picker dialog that selects the initial color.
*
* You can connect to the ::closed signal of this instance to get notified
* when the dialog is closed.
*/
ColorPickerDialog(const QColor &initial, QWidget *parent = nullptr);
~ColorPickerDialog();
/**
* @brief Return the final color selected by the user.
*
* Note that this method will always return the invalid color if the dialog
* is still open, or if the dialog has not been confirmed.
*
* @return The color selected by the user, if the dialog was confirmed.
* The invalid color, if the dialog has not been confirmed.
*/
QColor selectedColor() const;
pajlada::Signals::NoArgSignal closed;
protected:
void closeEvent(QCloseEvent *);
void themeChangedEvent();
private:
struct {
struct {
QLabel *label;
std::vector<ColorButton *> colors;
} recent;
struct {
QLabel *label;
std::vector<ColorButton *> colors;
} def;
struct {
QLabel *label;
ColorButton *color;
} selected;
struct {
QColorPicker *colorPicker;
QColorLuminancePicker *luminancePicker;
std::array<QLabel *, 4> spinBoxLabels;
std::array<QColSpinBox *, 4> spinBoxes;
QLabel *htmlLabel;
QLineEdit *htmlEdit;
} picker;
} ui_;
enum SpinBox : size_t { RED = 0, GREEN = 1, BLUE = 2, ALPHA = 3, END };
static const size_t MAX_RECENT_COLORS = 10;
static const size_t RECENT_COLORS_PER_ROW = 5;
static const size_t DEFAULT_COLORS_PER_ROW = 5;
QColor color_;
bool dialogConfirmed_;
QRegularExpressionValidator *htmlColorValidator_{};
/**
* @brief Update the currently selected color.
*
* @param color Color to update to.
* @param fromColorPicker Whether the color update has been triggered by
* one of the color picker widgets. This is needed
* to prevent weird widget behavior.
*/
void selectColor(const QColor &color, bool fromColorPicker);
/// Called when the dialog is confirmed.
void ok();
// Helper methods for initializing UI elements
void initRecentColors(LayoutCreator<QWidget> &creator);
void initDefaultColors(LayoutCreator<QWidget> &creator);
void initColorPicker(LayoutCreator<QWidget> &creator);
void initSpinBoxes(LayoutCreator<QWidget> &creator);
void initHtmlColor(LayoutCreator<QWidget> &creator);
};
} // namespace chatterino
+13 -1
View File
@@ -7,6 +7,7 @@
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "singletons/Emotes.hpp"
#include "util/Shortcut.hpp"
#include "widgets/Notebook.hpp"
#include "widgets/helper/ChannelView.hpp"
@@ -31,7 +32,14 @@ namespace {
if (!map.empty())
{
for (const auto &emote : map)
std::vector<std::pair<EmoteName, EmotePtr>> vec(map.begin(),
map.end());
std::sort(vec.begin(), vec.end(),
[](const std::pair<EmoteName, EmotePtr> &l,
const std::pair<EmoteName, EmotePtr> &r) {
return l.first.string < r.first.string;
});
for (const auto &emote : vec)
{
builder
.emplace<EmoteElement>(emote.second,
@@ -131,6 +139,10 @@ EmotePopup::EmotePopup(QWidget *parent)
this->viewEmojis_ = makeView("Emojis");
this->loadEmojis();
createWindowShortcut(this, "CTRL+Tab", [=] { notebook->selectNextTab(); });
createWindowShortcut(this, "CTRL+Shift+Tab",
[=] { notebook->selectPreviousTab(); });
}
void EmotePopup::loadChannel(ChannelPtr _channel)
+1 -1
View File
@@ -65,7 +65,7 @@ namespace {
// messageBox.setIcon(QMessageBox::Information);
// messageBox.setText("Successfully logged in with user <b>" +
// qS(username) + "</b>!");
auto basePath = fS("/accounts/uid{}", userID);
std::string basePath = "/accounts/uid" + userID.toStdString();
pajlada::Settings::Setting<QString>::set(basePath + "/username",
username);
pajlada::Settings::Setting<QString>::set(basePath + "/userID", userID);
+4 -56
View File
@@ -3,7 +3,6 @@
#include "IrcMessage"
#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"
@@ -55,7 +54,7 @@ void LogsPopup::getLogs()
dynamic_cast<TwitchChannel *>(this->channel_.get()))
{
this->channelName_ = twitchChannel->getName();
this->getLogviewerLogs(twitchChannel->roomId());
this->getOverrustleLogs();
return;
}
@@ -63,14 +62,11 @@ void LogsPopup::getLogs()
if (!this->channelName_.isEmpty())
{
PartialTwitchUser::byName(this->channelName_)
.getId(
[=](const QString &roomID) { this->getLogviewerLogs(roomID); },
this);
this->getOverrustleLogs();
return;
}
log("Unable to get logs, no channel name or something specified");
qDebug() << "Unable to get logs, no channel name or something specified";
}
void LogsPopup::setMessages(std::vector<MessagePtr> &messages)
@@ -81,57 +77,9 @@ void LogsPopup::setMessages(std::vector<MessagePtr> &messages)
SearchPopup::setChannel(logsChannel);
}
void LogsPopup::getLogviewerLogs(const QString &roomID)
{
auto url = QString("https://cbenni.com/api/logs/%1/?nick=%2&before=500")
.arg(this->channelName_, this->userName_);
NetworkRequest(url)
.caller(this)
.onError([this](NetworkResult) { this->getOverrustleLogs(); })
.onSuccess([this, roomID](auto result) -> Outcome {
auto data = result.parseJson();
std::vector<MessagePtr> messages;
QJsonValue before = data.value("before");
for (auto i : before.toArray())
{
auto messageObject = i.toObject();
QString message = messageObject.value("text").toString();
// Hacky way to fix the timestamp
message.insert(1, "historical=1;");
message.insert(1, QString("tmi-sent-ts=%10000;")
.arg(messageObject["time"].toInt()));
message.insert(1, QString("room-id=%1;").arg(roomID));
MessageParseArgs args;
auto ircMessage =
Communi::IrcMessage::fromData(message.toUtf8(), nullptr);
auto privMsg =
static_cast<Communi::IrcPrivateMessage *>(ircMessage);
TwitchMessageBuilder builder(this->channel_.get(), privMsg,
args);
builder.message().searchText = message;
messages.push_back(builder.build());
}
messages.push_back(
MessageBuilder(systemMessage,
"Logs provided by https://cbenni.com")
.release());
this->setMessages(messages);
return Success;
})
.execute();
}
void LogsPopup::getOverrustleLogs()
{
QString url =
auto url =
QString("https://overrustlelogs.net/api/v1/stalk/%1/%2.json?limit=500")
.arg(this->channelName_, this->userName_);
-1
View File
@@ -26,7 +26,6 @@ private:
void setMessages(std::vector<MessagePtr> &messages);
void getOverrustleLogs();
void getLogviewerLogs(const QString &roomID);
};
} // namespace chatterino
+1 -2
View File
@@ -1,5 +1,4 @@
#include "QualityPopup.hpp"
#include "debug/Log.hpp"
#include "util/StreamLink.hpp"
namespace chatterino {
@@ -50,7 +49,7 @@ void QualityPopup::okButtonClicked()
}
catch (const Exception &ex)
{
log("Exception caught trying to open streamlink: {}", ex.what());
qDebug() << "Exception caught trying to open streamlink:" << ex.what();
}
this->close();
+19 -1
View File
@@ -25,6 +25,7 @@
#define TEXT_VIEWS "Views: "
#define TEXT_CREATED "Created: "
#define TEXT_USER_ID "ID: "
#define TEXT_UNAVAILABLE "(not available)"
namespace chatterino {
namespace {
@@ -382,6 +383,22 @@ void UserInfoPopup::updateUserData()
{
std::weak_ptr<bool> hack = this->hack_;
const auto onIdFetchFailed = [this]() {
// this can occur when the account doesn't exist.
this->ui_.followerCountLabel->setText(TEXT_FOLLOWERS +
QString(TEXT_UNAVAILABLE));
this->ui_.viewCountLabel->setText(TEXT_VIEWS +
QString(TEXT_UNAVAILABLE));
this->ui_.createdDateLabel->setText(TEXT_CREATED +
QString(TEXT_UNAVAILABLE));
this->ui_.nameLabel->setText(this->userName_);
this->ui_.userIDLabel->setText(QString("ID") +
QString(TEXT_UNAVAILABLE));
this->ui_.userIDLabel->setProperty("copy-text",
QString(TEXT_UNAVAILABLE));
};
const auto onIdFetched = [this, hack](QString id) {
auto currentUser = getApp()->accounts->twitch.getCurrent();
@@ -462,7 +479,8 @@ void UserInfoPopup::updateUserData()
this->ui_.ignoreHighlights->setChecked(isIgnoringHighlights);
};
PartialTwitchUser::byName(this->userName_).getId(onIdFetched, this);
PartialTwitchUser::byName(this->userName_)
.getId(onIdFetched, onIdFetchFailed, this);
this->ui_.follow->setEnabled(false);
this->ui_.ignore->setEnabled(false);
+16 -21
View File
@@ -16,7 +16,6 @@
#include "common/Common.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "debug/Benchmark.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "messages/LimitedQueueSnapshot.hpp"
#include "messages/Message.hpp"
@@ -29,6 +28,7 @@
#include "singletons/Theme.hpp"
#include "singletons/TooltipPreviewImage.hpp"
#include "singletons/WindowManager.hpp"
#include "util/Clipboard.hpp"
#include "util/DistanceBetweenPoints.hpp"
#include "util/IncognitoBrowser.hpp"
#include "widgets/Scrollbar.hpp"
@@ -65,9 +65,8 @@ namespace {
if (!image->isEmpty())
{
copyMenu->addAction(
QString(scale) + "x link", [url = image->url()] {
QApplication::clipboard()->setText(url.string);
});
QString(scale) + "x link",
[url = image->url()] { crossPlatformCopy(url.string); });
openMenu->addAction(
QString(scale) + "x link", [url = image->url()] {
QDesktopServices::openUrl(QUrl(url.string));
@@ -85,9 +84,8 @@ namespace {
openMenu->addSeparator();
copyMenu->addAction(
"Copy " + name + " emote link", [url = emote.homePage] {
QApplication::clipboard()->setText(url.string); //
});
"Copy " + name + " emote link",
[url = emote.homePage] { crossPlatformCopy(url.string); });
openMenu->addAction(
"Open " + name + " emote link", [url = emote.homePage] {
QDesktopServices::openUrl(QUrl(url.string)); //
@@ -125,9 +123,8 @@ ChannelView::ChannelView(BaseWidget *parent)
});
auto shortcut = new QShortcut(QKeySequence("Ctrl+C"), this);
QObject::connect(shortcut, &QShortcut::activated, [this] {
QGuiApplication::clipboard()->setText(this->getSelectedText());
});
QObject::connect(shortcut, &QShortcut::activated,
[this] { crossPlatformCopy(this->getSelectedText()); });
this->clickTimer_ = new QTimer(this);
this->clickTimer_->setSingleShot(true);
@@ -651,7 +648,8 @@ void ChannelView::messageAppended(MessagePtr &message,
if (!messageFlags->has(MessageFlag::DoNotTriggerNotification))
{
if (messageFlags->has(MessageFlag::Highlighted))
if (messageFlags->has(MessageFlag::Highlighted) &&
!messageFlags->has(MessageFlag::Subscription))
{
this->tabHighlightRequested.invoke(HighlightState::Highlighted);
}
@@ -740,9 +738,8 @@ void ChannelView::messageReplaced(size_t index, MessagePtr &replacement)
auto snapshot = this->messages_.getSnapshot();
if (index >= snapshot.size())
{
log("Tried to replace out of bounds message. Index: {}. "
"Length: {}",
index, snapshot.size());
qDebug() << "Tried to replace out of bounds message. Index:" << index
<< ". Length:" << snapshot.size();
return;
}
@@ -1554,8 +1551,7 @@ void ChannelView::addContextMenuItems(
menu->addAction("Open link incognito",
[url] { openLinkIncognito(url); });
}
menu->addAction("Copy link",
[url] { QApplication::clipboard()->setText(url); });
menu->addAction("Copy link", [url] { crossPlatformCopy(url); });
menu->addSeparator();
}
@@ -1563,9 +1559,8 @@ void ChannelView::addContextMenuItems(
// Copy actions
if (!this->selection_.isEmpty())
{
menu->addAction("Copy selection", [this] {
QGuiApplication::clipboard()->setText(this->getSelectedText());
});
menu->addAction("Copy selection",
[this] { crossPlatformCopy(this->getSelectedText()); });
}
menu->addAction("Copy message", [layout] {
@@ -1573,14 +1568,14 @@ void ChannelView::addContextMenuItems(
layout->addSelectionText(copyString, 0, INT_MAX,
CopyMode::OnlyTextAndEmotes);
QGuiApplication::clipboard()->setText(copyString);
crossPlatformCopy(copyString);
});
menu->addAction("Copy full message", [layout] {
QString copyString;
layout->addSelectionText(copyString);
QGuiApplication::clipboard()->setText(copyString);
crossPlatformCopy(copyString);
});
// Open in new split.
+23
View File
@@ -0,0 +1,23 @@
#include "widgets/helper/ColorButton.hpp"
namespace chatterino {
ColorButton::ColorButton(const QColor &color, QWidget *parent)
: QPushButton(parent)
, color_(color)
{
this->setColor(color_);
}
const QColor &ColorButton::color() const
{
return this->color_;
}
void ColorButton::setColor(QColor color)
{
this->color_ = color;
this->setStyleSheet("background-color: " + color.name(QColor::HexArgb));
}
} // namespace chatterino
+18
View File
@@ -0,0 +1,18 @@
#pragma once
namespace chatterino {
class ColorButton : public QPushButton
{
public:
ColorButton(const QColor &color, QWidget *parent = nullptr);
const QColor &color() const;
void setColor(QColor color);
private:
QColor color_;
};
} // namespace chatterino
-1
View File
@@ -2,7 +2,6 @@
#include "Application.hpp"
#include "common/Common.hpp"
#include "debug/Log.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
+290
View File
@@ -0,0 +1,290 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtWidgets module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "widgets/helper/QColorPicker.hpp"
#include <qdrawutil.h>
/*
* These classes are literally copied from the Qt source.
* Unfortunately, they are private to the QColorDialog class so we cannot use
* them directly.
* If they become public at any point in the future, it should be possible to
* replace every include of this header with the respective includes for the
* QColorPicker, QColorLuminancePicker, and QColSpinBox classes.
*/
namespace chatterino {
int QColorLuminancePicker::y2val(int y)
{
int d = height() - 2 * coff - 1;
return 255 - (y - coff) * 255 / d;
}
int QColorLuminancePicker::val2y(int v)
{
int d = height() - 2 * coff - 1;
return coff + (255 - v) * d / 255;
}
QColorLuminancePicker::QColorLuminancePicker(QWidget *parent)
: QWidget(parent)
{
hue = 100;
val = 100;
sat = 100;
pix = 0;
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
}
QColorLuminancePicker::~QColorLuminancePicker()
{
delete pix;
}
void QColorLuminancePicker::mouseMoveEvent(QMouseEvent *m)
{
setVal(y2val(m->y()));
}
void QColorLuminancePicker::mousePressEvent(QMouseEvent *m)
{
setVal(y2val(m->y()));
}
void QColorLuminancePicker::setVal(int v)
{
if (val == v)
return;
val = qMax(0, qMin(v, 255));
delete pix;
pix = 0;
repaint();
emit newHsv(hue, sat, val);
}
//receives from a hue,sat chooser and relays.
void QColorLuminancePicker::setCol(int h, int s)
{
setCol(h, s, val);
emit newHsv(h, s, val);
}
QSize QColorLuminancePicker::sizeHint() const
{
return QSize(LUMINANCE_PICKER_WIDTH, LUMINANCE_PICKER_HEIGHT);
}
void QColorLuminancePicker::paintEvent(QPaintEvent *)
{
int w = width() - 5;
QRect r(0, foff, w, height() - 2 * foff);
int wi = r.width() - 2;
int hi = r.height() - 2;
if (!pix || pix->height() != hi || pix->width() != wi)
{
delete pix;
QImage img(wi, hi, QImage::Format_RGB32);
int y;
uint *pixel = (uint *)img.scanLine(0);
for (y = 0; y < hi; y++)
{
uint *end = pixel + wi;
std::fill(pixel, end,
QColor::fromHsv(hue, sat, y2val(y + coff)).rgb());
pixel = end;
}
pix = new QPixmap(QPixmap::fromImage(img));
}
QPainter p(this);
p.drawPixmap(1, coff, *pix);
const QPalette &g = palette();
qDrawShadePanel(&p, r, g, true);
p.setPen(g.windowText().color());
p.setBrush(g.windowText());
QPolygon a;
int y = val2y(val);
a.setPoints(3, w, y, w + 5, y + 5, w + 5, y - 5);
p.eraseRect(w, 0, 5, height());
p.drawPolygon(a);
}
void QColorLuminancePicker::setCol(int h, int s, int v)
{
val = v;
hue = h;
sat = s;
delete pix;
pix = 0;
repaint();
}
QPoint QColorPicker::colPt()
{
QRect r = contentsRect();
return QPoint((360 - hue) * (r.width() - 1) / 360,
(255 - sat) * (r.height() - 1) / 255);
}
int QColorPicker::huePt(const QPoint &pt)
{
QRect r = contentsRect();
return 360 - pt.x() * 360 / (r.width() - 1);
}
int QColorPicker::satPt(const QPoint &pt)
{
QRect r = contentsRect();
return 255 - pt.y() * 255 / (r.height() - 1);
}
void QColorPicker::setCol(const QPoint &pt)
{
setCol(huePt(pt), satPt(pt));
}
QColorPicker::QColorPicker(QWidget *parent)
: QFrame(parent)
, crossVisible(true)
{
hue = 0;
sat = 0;
setCol(150, 255);
setAttribute(Qt::WA_NoSystemBackground);
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
}
QColorPicker::~QColorPicker()
{
}
void QColorPicker::setCrossVisible(bool visible)
{
if (crossVisible != visible)
{
crossVisible = visible;
update();
}
}
QSize QColorPicker::sizeHint() const
{
return QSize(COLOR_PICKER_WIDTH, COLOR_PICKER_HEIGHT);
}
void QColorPicker::setCol(int h, int s)
{
int nhue = qMin(qMax(0, h), 359);
int nsat = qMin(qMax(0, s), 255);
if (nhue == hue && nsat == sat)
return;
QRect r(colPt(), QSize(20, 20));
hue = nhue;
sat = nsat;
r = r.united(QRect(colPt(), QSize(20, 20)));
r.translate(contentsRect().x() - 9, contentsRect().y() - 9);
repaint(r);
}
void QColorPicker::mouseMoveEvent(QMouseEvent *m)
{
QPoint p = m->pos() - contentsRect().topLeft();
setCol(p);
emit newCol(hue, sat);
}
void QColorPicker::mousePressEvent(QMouseEvent *m)
{
QPoint p = m->pos() - contentsRect().topLeft();
setCol(p);
emit newCol(hue, sat);
}
void QColorPicker::paintEvent(QPaintEvent *)
{
QPainter p(this);
drawFrame(&p);
QRect r = contentsRect();
p.drawPixmap(r.topLeft(), pix);
if (crossVisible)
{
QPoint pt = colPt() + r.topLeft();
p.setPen(Qt::black);
p.fillRect(pt.x() - 9, pt.y(), 20, 2, Qt::black);
p.fillRect(pt.x(), pt.y() - 9, 2, 20, Qt::black);
}
}
void QColorPicker::resizeEvent(QResizeEvent *ev)
{
QFrame::resizeEvent(ev);
int w = width() - frameWidth() * 2;
int h = height() - frameWidth() * 2;
QImage img(w, h, QImage::Format_RGB32);
int x, y;
uint *pixel = (uint *)img.scanLine(0);
for (y = 0; y < h; y++)
{
const uint *end = pixel + w;
x = 0;
while (pixel < end)
{
QPoint p(x, y);
QColor c;
c.setHsv(huePt(p), satPt(p), 200);
*pixel = c.rgb();
++pixel;
++x;
}
}
pix = QPixmap::fromImage(img);
}
QColSpinBox::QColSpinBox(QWidget *parent)
: QSpinBox(parent)
{
this->setRange(0, 255);
}
void QColSpinBox::setValue(int i)
{
const QSignalBlocker blocker(this);
QSpinBox::setValue(i);
}
} // namespace chatterino
+130
View File
@@ -0,0 +1,130 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtWidgets module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#pragma once
#include <QSpinBox>
namespace chatterino {
/*
* These classes are literally copied from the Qt source.
* Unfortunately, they are private to the QColorDialog class so we cannot use
* them directly.
* If they become public at any point in the future, it should be possible to
* replace every include of this header with the respective includes for the
* QColorPicker, QColorLuminancePicker, and QColSpinBox classes.
*/
class QColorPicker : public QFrame
{
Q_OBJECT
public:
QColorPicker(QWidget *parent);
~QColorPicker();
void setCrossVisible(bool visible);
public slots:
void setCol(int h, int s);
signals:
void newCol(int h, int s);
protected:
QSize sizeHint() const override;
void paintEvent(QPaintEvent *) override;
void mouseMoveEvent(QMouseEvent *) override;
void mousePressEvent(QMouseEvent *) override;
void resizeEvent(QResizeEvent *) override;
private:
int hue;
int sat;
QPoint colPt();
int huePt(const QPoint &pt);
int satPt(const QPoint &pt);
void setCol(const QPoint &pt);
QPixmap pix;
bool crossVisible;
};
static const int COLOR_PICKER_WIDTH = 220;
static const int COLOR_PICKER_HEIGHT = 200;
class QColorLuminancePicker : public QWidget
{
Q_OBJECT
public:
QColorLuminancePicker(QWidget *parent = 0);
~QColorLuminancePicker();
public slots:
void setCol(int h, int s, int v);
void setCol(int h, int s);
signals:
void newHsv(int h, int s, int v);
protected:
QSize sizeHint() const override;
void paintEvent(QPaintEvent *) override;
void mouseMoveEvent(QMouseEvent *) override;
void mousePressEvent(QMouseEvent *) override;
private:
enum { foff = 3, coff = 4 }; //frame and contents offset
int val;
int hue;
int sat;
int y2val(int y);
int val2y(int val);
void setVal(int v);
QPixmap *pix;
};
static const int LUMINANCE_PICKER_WIDTH = 25;
static const int LUMINANCE_PICKER_HEIGHT = COLOR_PICKER_HEIGHT;
class QColSpinBox : public QSpinBox
{
public:
QColSpinBox(QWidget *parent);
void setValue(int i);
};
} // namespace chatterino
+7 -4
View File
@@ -1,24 +1,27 @@
#include "widgets/helper/ScrollbarHighlight.hpp"
#include "Application.hpp"
#include "singletons/Theme.hpp"
#include "widgets/Scrollbar.hpp"
namespace chatterino {
ScrollbarHighlight::ScrollbarHighlight()
: color_(Color::Highlight)
: color_(std::make_shared<QColor>())
, style_(Style::None)
{
}
ScrollbarHighlight::ScrollbarHighlight(Color color, Style style)
ScrollbarHighlight::ScrollbarHighlight(const std::shared_ptr<QColor> color,
Style style)
: color_(color)
, style_(style)
{
}
ScrollbarHighlight::Color ScrollbarHighlight::getColor() const
QColor ScrollbarHighlight::getColor() const
{
return this->color_;
return *this->color_;
}
ScrollbarHighlight::Style ScrollbarHighlight::getStyle() const
+12 -4
View File
@@ -6,17 +6,25 @@ class ScrollbarHighlight
{
public:
enum Style : char { None, Default, Line };
enum Color : char { Highlight, Subscription };
/**
* @brief Constructs an invalid ScrollbarHighlight.
*
* A highlight constructed this way will not show on the scrollbar.
* For these, use the static ScrollbarHighlight::newSubscription and
* ScrollbarHighlight::newHighlight methods.
*/
ScrollbarHighlight();
ScrollbarHighlight(Color color, Style style = Default);
Color getColor() const;
ScrollbarHighlight(const std::shared_ptr<QColor> color,
Style style = Default);
QColor getColor() const;
Style getStyle() const;
bool isNull() const;
private:
Color color_;
std::shared_ptr<QColor> color_;
Style style_;
};
+1 -4
View File
@@ -2,7 +2,6 @@
#include "common/Modes.hpp"
#include "common/Version.hpp"
#include "debug/Log.hpp"
#include "util/LayoutCreator.hpp"
#include "util/RemoveScrollAreaBackground.hpp"
#include "widgets/helper/SignalLabel.hpp"
@@ -102,8 +101,6 @@ AboutPage::AboutPage()
":/licenses/qt_lgpl-3.0.txt");
addLicense(form.getElement(), "Boost", "https://www.boost.org/",
":/licenses/boost_boost.txt");
addLicense(form.getElement(), "Fmt", "http://fmtlib.net/",
":/licenses/fmt_bsd2.txt");
addLicense(form.getElement(), "LibCommuni",
"https://github.com/communi/libcommuni",
":/licenses/libcommuni_BSD3.txt");
@@ -166,7 +163,7 @@ AboutPage::AboutPage()
if (contributorParts.size() != 4)
{
log("Missing parts in line '{}'", line);
qDebug() << "Missing parts in line" << line;
continue;
}
+27
View File
@@ -547,6 +547,33 @@ void GeneralPage::initLayout(SettingsLayout &layout)
QDesktopServices::openUrl(getPaths()->rootAppDataDirectory);
});
layout.addTitle("Similarity");
layout.addDescription(
"Hides or grays out similar messages from the same user");
layout.addCheckbox("Similarity enabled", s.similarityEnabled);
layout.addCheckbox("Gray out similar messages", s.colorSimilarDisabled);
layout.addCheckbox("Hide similar messages (Ctrl + H)", s.hideSimilar);
layout.addCheckbox("Hide or gray out my own similar messages",
s.hideSimilarMyself);
layout.addCheckbox("Shown similar messages trigger highlights",
s.shownSimilarTriggerHighlights);
layout.addDropdown<float>(
"Similarity percentage", {"0.5", "0.75", "0.9"}, s.similarityPercentage,
[](auto val) { return QString::number(val); },
[](auto args) { return fuzzyToFloat(args.value, 0.9f); });
s.hideSimilar.connect(
[]() { getApp()->windows->forceLayoutChannelViews(); }, false);
layout.addDropdown<int>(
"Similar messages max delay in seconds",
{"5", "10", "15", "30", "60", "120"}, s.hideSimilarMaxDelay,
[](auto val) { return QString::number(val); },
[](auto args) { return fuzzyToInt(args.value, 5); });
layout.addDropdown<int>(
"Similar messages max previous messages to check",
{"1", "2", "3", "4", "5"}, s.hideSimilarMaxMessagesToCheck,
[](auto val) { return QString::number(val); },
[](auto args) { return fuzzyToInt(args.value, 3); });
// invisible element for width
auto inv = new BaseWidget(this);
inv->setScaleIndependantWidth(500);
+122 -20
View File
@@ -5,11 +5,11 @@
#include "controllers/highlights/HighlightController.hpp"
#include "controllers/highlights/HighlightModel.hpp"
#include "controllers/highlights/UserHighlightModel.hpp"
#include "debug/Log.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "util/LayoutCreator.hpp"
#include "util/StandardItemHelper.hpp"
#include "widgets/helper/EditableModelView.hpp"
#include "widgets/dialogs/ColorPickerDialog.hpp"
#include <QFileDialog>
#include <QHeaderView>
@@ -46,8 +46,10 @@ HighlightingPage::HighlightingPage()
// HIGHLIGHTS
auto highlights = tabs.appendTab(new QVBoxLayout, "Messages");
{
highlights.emplace<QLabel>("Messages can be highlighted if "
"they match a certain pattern.");
highlights.emplace<QLabel>(
"Messages can be highlighted if they match a certain "
"pattern.\n"
"NOTE: User highlights will override phrase highlights.");
EditableModelView *view =
highlights
@@ -57,7 +59,8 @@ HighlightingPage::HighlightingPage()
view->addRegexHelpLink();
view->setTitles({"Pattern", "Flash\ntaskbar", "Play\nsound",
"Enable\nregex", "Case-\nsensitive"});
"Enable\nregex", "Case-\nsensitive",
"Custom\nsound", "Color"});
view->getTableView()->horizontalHeader()->setSectionResizeMode(
QHeaderView::Fixed);
view->getTableView()->horizontalHeader()->setSectionResizeMode(
@@ -72,14 +75,22 @@ HighlightingPage::HighlightingPage()
view->addButtonPressed.connect([] {
getApp()->highlights->phrases.appendItem(HighlightPhrase{
"my phrase", true, false, false, false});
"my phrase", true, false, false, false, "",
*ColorProvider::instance().color(
ColorType::SelfHighlight)});
});
QObject::connect(view->getTableView(), &QTableView::clicked,
[this, view](const QModelIndex &clicked) {
this->tableCellClicked(clicked, view);
});
}
auto pingUsers = tabs.appendTab(new QVBoxLayout, "Users");
{
pingUsers.emplace<QLabel>(
"Messages from a certain user can be highlighted.");
"Messages from a certain user can be highlighted.\n"
"NOTE: User highlights will override phrase highlights.");
EditableModelView *view =
pingUsers
.emplace<EditableModelView>(
@@ -90,9 +101,10 @@ HighlightingPage::HighlightingPage()
view->getTableView()->horizontalHeader()->hideSection(4);
// Case-sensitivity doesn't make sense for user names so it is
// set to "false" by default & no checkbox is shown
// set to "false" by default & the column is hidden
view->setTitles({"Username", "Flash\ntaskbar", "Play\nsound",
"Enable\nregex"});
"Enable\nregex", "Case-\nsensitive",
"Custom\nsound", "Color"});
view->getTableView()->horizontalHeader()->setSectionResizeMode(
QHeaderView::Fixed);
view->getTableView()->horizontalHeader()->setSectionResizeMode(
@@ -108,8 +120,15 @@ HighlightingPage::HighlightingPage()
view->addButtonPressed.connect([] {
getApp()->highlights->highlightedUsers.appendItem(
HighlightPhrase{"highlighted user", true, false, false,
false});
false, "",
*ColorProvider::instance().color(
ColorType::SelfHighlight)});
});
QObject::connect(view->getTableView(), &QTableView::clicked,
[this, view](const QModelIndex &clicked) {
this->tableCellClicked(clicked, view);
});
}
auto disabledUsers =
@@ -148,17 +167,33 @@ HighlightingPage::HighlightingPage()
// MISC
auto customSound = layout.emplace<QHBoxLayout>().withoutMargin();
{
customSound.append(this->createCheckBox(
"Custom sound", getSettings()->customHighlightSound));
auto fallbackSound = customSound.append(this->createCheckBox(
"Fallback sound (played when no other sound is set)",
getSettings()->customHighlightSound));
auto getSelectFileText = [] {
const QString value = getSettings()->pathHighlightSound;
return value.isEmpty() ? "Select custom fallback sound"
: QUrl::fromLocalFile(value).fileName();
};
auto selectFile =
customSound.emplace<QPushButton>("Select custom sound file");
QObject::connect(selectFile.getElement(), &QPushButton::clicked,
this, [this] {
auto fileName = QFileDialog::getOpenFileName(
this, tr("Open Sound"), "",
tr("Audio Files (*.mp3 *.wav)"));
getSettings()->pathHighlightSound = fileName;
});
customSound.emplace<QPushButton>(getSelectFileText());
QObject::connect(
selectFile.getElement(), &QPushButton::clicked, this,
[=]() mutable {
auto fileName = QFileDialog::getOpenFileName(
this, tr("Open Sound"), "",
tr("Audio Files (*.mp3 *.wav)"));
getSettings()->pathHighlightSound = fileName;
selectFile.getElement()->setText(getSelectFileText());
// Set check box according to updated value
fallbackSound->setCheckState(
fileName.isEmpty() ? Qt::Unchecked : Qt::Checked);
});
}
layout.append(createCheckBox(ALWAYS_PLAY,
@@ -172,4 +207,71 @@ HighlightingPage::HighlightingPage()
this->disabledUsersChangedTimer_.setSingleShot(true);
}
void HighlightingPage::tableCellClicked(const QModelIndex &clicked,
EditableModelView *view)
{
using Column = HighlightModel::Column;
if (clicked.column() == Column::SoundPath)
{
auto fileUrl = QFileDialog::getOpenFileUrl(
this, tr("Open Sound"), QUrl(), tr("Audio Files (*.mp3 *.wav)"));
view->getModel()->setData(clicked, fileUrl, Qt::UserRole);
view->getModel()->setData(clicked, fileUrl.fileName(), Qt::DisplayRole);
// Enable custom sound check box if user set a sound
if (!fileUrl.isEmpty())
{
QModelIndex checkBox = clicked.siblingAtColumn(Column::PlaySound);
view->getModel()->setData(checkBox, Qt::Checked,
Qt::CheckStateRole);
}
}
else if (clicked.column() == Column::Color)
{
auto initial =
view->getModel()->data(clicked, Qt::DecorationRole).value<QColor>();
auto dialog = new ColorPickerDialog(initial, this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
dialog->closed.connect([=] {
QColor selected = dialog->selectedColor();
if (selected.isValid())
{
view->getModel()->setData(clicked, selected,
Qt::DecorationRole);
// Hacky (?) way to figure out what tab the cell was clicked in
const bool fromMessages = (dynamic_cast<HighlightModel *>(
view->getModel()) != nullptr);
if (fromMessages)
{
/*
* For preset highlights in the "Messages" tab, we need to
* manually update the color map.
*/
auto instance = ColorProvider::instance();
switch (clicked.row())
{
case 0:
instance.updateColor(ColorType::SelfHighlight,
selected);
break;
case 1:
instance.updateColor(ColorType::Whisper, selected);
break;
case 2:
instance.updateColor(ColorType::Subscription,
selected);
break;
}
}
}
});
}
}
} // namespace chatterino
@@ -1,5 +1,6 @@
#pragma once
#include "widgets/helper/EditableModelView.hpp"
#include "widgets/settingspages/SettingsPage.hpp"
#include <QAbstractTableModel>
@@ -17,6 +18,8 @@ public:
private:
QTimer disabledUsersChangedTimer_;
void tableCellClicked(const QModelIndex &clicked, EditableModelView *view);
};
} // namespace chatterino
@@ -26,6 +26,10 @@ KeyboardSettingsPage::KeyboardSettingsPage()
form->addRow(new QLabel("Ctrl + Shift + T"), new QLabel("Create new tab"));
form->addRow(new QLabel("Ctrl + Shift + W"),
new QLabel("Close current tab"));
form->addRow(
new QLabel("Ctrl + H"),
new QLabel(
"Hide/Show similar messages (Enable in General under Similarity)"));
form->addItem(new QSpacerItem(16, 16));
form->addRow(new QLabel("Ctrl + 1/2/3/..."),
+3 -3
View File
@@ -4,7 +4,6 @@
#include "common/Env.hpp"
#include "common/NetworkRequest.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/EmoteValue.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
@@ -13,6 +12,7 @@
#include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp"
#include "util/NuulsUploader.hpp"
#include "util/Clipboard.hpp"
#include "util/Shortcut.hpp"
#include "util/StreamLink.hpp"
#include "widgets/Notebook.hpp"
@@ -537,7 +537,7 @@ void Split::openInStreamlink()
}
catch (const Exception &ex)
{
log("Error in doOpenStreamlink: {}", ex.what());
qDebug() << "Error in doOpenStreamlink:" << ex.what();
}
}
@@ -667,7 +667,7 @@ void Split::openSubPage()
void Split::copyToClipboard()
{
QApplication::clipboard()->setText(this->view_->getSelectedText());
crossPlatformCopy(this->view_->getSelectedText());
}
void Split::showSearch()
+6 -1
View File
@@ -577,10 +577,15 @@ void SplitHeader::updateModerationModeIcon()
auto channel = this->split_->getChannel();
auto twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());
if (twitchChannel != nullptr && twitchChannel->hasModRights())
if (twitchChannel != nullptr &&
(twitchChannel->hasModRights() || moderationMode))
{
this->moderationButton_->show();
}
else
{
this->moderationButton_->hide();
}
}
void SplitHeader::paintEvent(QPaintEvent *)