Merge branch 'master' into irc-support

This commit is contained in:
fourtf 2019-09-18 13:03:16 +02:00
commit 3ab7362304
204 changed files with 17026 additions and 16467 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
# Normalize line endings to LF for files that Git detects as text
* text=auto

7
.gitmodules vendored
View file

@ -4,10 +4,6 @@
[submodule "lib/humanize"] [submodule "lib/humanize"]
path = lib/humanize path = lib/humanize
url = https://github.com/pajlada/humanize.git url = https://github.com/pajlada/humanize.git
[submodule "lib/websocketpp"]
path = lib/websocketpp
url = https://github.com/zaphoyd/websocketpp.git
branch = develop
[submodule "lib/qBreakpad"] [submodule "lib/qBreakpad"]
path = lib/qBreakpad path = lib/qBreakpad
url = https://github.com/jiakuan/qBreakpad.git url = https://github.com/jiakuan/qBreakpad.git
@ -29,3 +25,6 @@
[submodule "lib/qtkeychain"] [submodule "lib/qtkeychain"]
path = lib/qtkeychain path = lib/qtkeychain
url = https://github.com/Chatterino/qtkeychain url = https://github.com/Chatterino/qtkeychain
[submodule "lib/websocketpp"]
path = lib/websocketpp
url = https://github.com/ziocleto/websocketpp

View file

@ -40,6 +40,8 @@ build_script:
cp release/chatterino.exe Chatterino2/ cp release/chatterino.exe Chatterino2/
echo nightly > Chatterino2/modes
7z a chatterino-windows-x86-64.zip Chatterino2/ 7z a chatterino-windows-x86-64.zip Chatterino2/
artifacts: artifacts:
- path: build/chatterino-windows-x86-64.zip - path: build/chatterino-windows-x86-64.zip
@ -48,7 +50,7 @@ deploy:
- provider: GitHub - provider: GitHub
tag: nightly-build tag: nightly-build
release: nightly-build release: nightly-build
description: 'nightly v$(appveyor_build_version) built $(APPVEYOR_REPO_COMMIT_TIMESTAMP)\nLast change: $(APPVEYOR_REPO_COMMIT_MESSAGE) \n$(APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED)' description: 'nightly v$(appveyor_build_version) built 👉 $(APPVEYOR_REPO_COMMIT_TIMESTAMP) 👈\nLast change: $(APPVEYOR_REPO_COMMIT_MESSAGE) \n$(APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED)'
auth_token: auth_token:
secure: sAJzAbiQSsYZLT+byDar9u61X0E9o35anaPMSFkOzdHeDFHjx1kW4cDP/4EEbxhx secure: sAJzAbiQSsYZLT+byDar9u61X0E9o35anaPMSFkOzdHeDFHjx1kW4cDP/4EEbxhx
repository: Chatterino/chatterino2 repository: Chatterino/chatterino2

View file

@ -83,6 +83,7 @@ SOURCES += \
src/common/DownloadManager.cpp \ src/common/DownloadManager.cpp \
src/common/Env.cpp \ src/common/Env.cpp \
src/common/LinkParser.cpp \ src/common/LinkParser.cpp \
src/common/Modes.cpp \
src/common/NetworkManager.cpp \ src/common/NetworkManager.cpp \
src/common/NetworkPrivate.cpp \ src/common/NetworkPrivate.cpp \
src/common/NetworkRequest.cpp \ src/common/NetworkRequest.cpp \
@ -123,12 +124,14 @@ SOURCES += \
src/messages/MessageColor.cpp \ src/messages/MessageColor.cpp \
src/messages/MessageContainer.cpp \ src/messages/MessageContainer.cpp \
src/messages/MessageElement.cpp \ src/messages/MessageElement.cpp \
src/messages/search/AuthorPredicate.cpp \
src/messages/search/LinkPredicate.cpp \
src/messages/search/SubstringPredicate.cpp \
src/providers/bttv/BttvEmotes.cpp \ src/providers/bttv/BttvEmotes.cpp \
src/providers/bttv/LoadBttvChannelEmote.cpp \ src/providers/bttv/LoadBttvChannelEmote.cpp \
src/providers/chatterino/ChatterinoBadges.cpp \ src/providers/chatterino/ChatterinoBadges.cpp \
src/providers/emoji/Emojis.cpp \ src/providers/emoji/Emojis.cpp \
src/providers/ffz/FfzEmotes.cpp \ src/providers/ffz/FfzEmotes.cpp \
src/providers/ffz/FfzModBadge.cpp \
src/providers/irc/AbstractIrcServer.cpp \ src/providers/irc/AbstractIrcServer.cpp \
src/providers/irc/Irc2.cpp \ src/providers/irc/Irc2.cpp \
src/providers/irc/IrcAccount.cpp \ src/providers/irc/IrcAccount.cpp \
@ -239,6 +242,7 @@ HEADERS += \
src/common/DownloadManager.hpp \ src/common/DownloadManager.hpp \
src/common/Env.hpp \ src/common/Env.hpp \
src/common/LinkParser.hpp \ src/common/LinkParser.hpp \
src/common/Modes.hpp \
src/common/NetworkCommon.hpp \ src/common/NetworkCommon.hpp \
src/common/NetworkManager.hpp \ src/common/NetworkManager.hpp \
src/common/NetworkPrivate.hpp \ src/common/NetworkPrivate.hpp \
@ -276,6 +280,7 @@ HEADERS += \
src/controllers/taggedusers/TaggedUser.hpp \ src/controllers/taggedusers/TaggedUser.hpp \
src/controllers/taggedusers/TaggedUsersController.hpp \ src/controllers/taggedusers/TaggedUsersController.hpp \
src/controllers/taggedusers/TaggedUsersModel.hpp \ src/controllers/taggedusers/TaggedUsersModel.hpp \
src/ForwardDecl.hpp \
src/messages/Emote.hpp \ src/messages/Emote.hpp \
src/messages/Image.hpp \ src/messages/Image.hpp \
src/messages/ImageSet.hpp \ src/messages/ImageSet.hpp \
@ -291,6 +296,10 @@ HEADERS += \
src/messages/MessageContainer.hpp \ src/messages/MessageContainer.hpp \
src/messages/MessageElement.hpp \ src/messages/MessageElement.hpp \
src/messages/MessageParseArgs.hpp \ src/messages/MessageParseArgs.hpp \
src/messages/search/AuthorPredicate.hpp \
src/messages/search/LinkPredicate.hpp \
src/messages/search/MessagePredicate.hpp \
src/messages/search/SubstringPredicate.hpp \
src/messages/Selection.hpp \ src/messages/Selection.hpp \
src/PrecompiledHeader.hpp \ src/PrecompiledHeader.hpp \
src/providers/bttv/BttvEmotes.hpp \ src/providers/bttv/BttvEmotes.hpp \
@ -298,7 +307,6 @@ HEADERS += \
src/providers/chatterino/ChatterinoBadges.hpp \ src/providers/chatterino/ChatterinoBadges.hpp \
src/providers/emoji/Emojis.hpp \ src/providers/emoji/Emojis.hpp \
src/providers/ffz/FfzEmotes.hpp \ src/providers/ffz/FfzEmotes.hpp \
src/providers/ffz/FfzModBadge.hpp \
src/providers/irc/AbstractIrcServer.hpp \ src/providers/irc/AbstractIrcServer.hpp \
src/providers/irc/Irc2.hpp \ src/providers/irc/Irc2.hpp \
src/providers/irc/IrcAccount.hpp \ src/providers/irc/IrcAccount.hpp \
@ -356,6 +364,7 @@ HEADERS += \
src/util/rangealgorithm.hpp \ src/util/rangealgorithm.hpp \
src/util/RapidjsonHelpers.hpp \ src/util/RapidjsonHelpers.hpp \
src/util/RemoveScrollAreaBackground.hpp \ src/util/RemoveScrollAreaBackground.hpp \
src/util/SampleCheerMessages.hpp \
src/util/SharedPtrElementLess.hpp \ src/util/SharedPtrElementLess.hpp \
src/util/StandardItemHelper.hpp \ src/util/StandardItemHelper.hpp \
src/util/StreamLink.hpp \ src/util/StreamLink.hpp \

View file

@ -44,12 +44,12 @@
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
BaseWindow::BaseWindow(QWidget *parent, Flags _flags) BaseWindow::BaseWindow(FlagsEnum<Flags> _flags, QWidget *parent)
: BaseWidget(parent, : BaseWidget(parent,
Qt::Window | ((_flags & TopMost) ? Qt::WindowStaysOnTopHint Qt::Window | (_flags.has(TopMost) ? Qt::WindowStaysOnTopHint
: Qt::WindowFlags())) : Qt::WindowFlags()))
, enableCustomFrame_(_flags & EnableCustomFrame) , enableCustomFrame_(_flags.has(EnableCustomFrame))
, frameless_(_flags & Frameless) , frameless_(_flags.has(Frameless))
, flags_(_flags) , flags_(_flags)
{ {
if (this->frameless_) if (this->frameless_)
@ -74,9 +74,13 @@ BaseWindow::BaseWindow(QWidget *parent, Flags _flags)
createWindowShortcut(this, "CTRL+0", createWindowShortcut(this, "CTRL+0",
[] { getSettings()->uiScale.setValue(1); }); [] { getSettings()->uiScale.setValue(1); });
// QTimer::this->scaleChangedEvent(this->getScale());
this->resize(300, 150); this->resize(300, 150);
#ifdef USEWINSDK
this->useNextBounds_.setSingleShot(true);
QObject::connect(&this->useNextBounds_, &QTimer::timeout, this,
[this]() { this->currentBounds_ = this->nextBounds_; });
#endif
} }
void BaseWindow::setInitialBounds(const QRect &bounds) void BaseWindow::setInitialBounds(const QRect &bounds)
@ -107,11 +111,6 @@ float BaseWindow::qtFontScale() const
return this->scale() / this->nativeScale_; return this->scale() / this->nativeScale_;
} }
BaseWindow::Flags BaseWindow::getFlags()
{
return this->flags_;
}
void BaseWindow::init() void BaseWindow::init()
{ {
this->setWindowIcon(QIcon(":/images/icon.png")); this->setWindowIcon(QIcon(":/images/icon.png"));
@ -200,7 +199,7 @@ void BaseWindow::init()
#ifdef USEWINSDK #ifdef USEWINSDK
// fourtf: don't ask me why we need to delay this // fourtf: don't ask me why we need to delay this
if (!(this->flags_ & Flags::TopMost)) if (!this->flags_.has(TopMost))
{ {
QTimer::singleShot(1, this, [this] { QTimer::singleShot(1, this, [this] {
getSettings()->windowTopMost.connect( getSettings()->windowTopMost.connect(
@ -364,7 +363,7 @@ void BaseWindow::onFocusLost()
void BaseWindow::mousePressEvent(QMouseEvent *event) void BaseWindow::mousePressEvent(QMouseEvent *event)
{ {
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
if (this->flags_ & FramelessDraggable) if (this->flags_.has(FramelessDraggable))
{ {
this->movingRelativePos = event->localPos(); this->movingRelativePos = event->localPos();
if (auto widget = if (auto widget =
@ -400,7 +399,7 @@ void BaseWindow::mousePressEvent(QMouseEvent *event)
void BaseWindow::mouseReleaseEvent(QMouseEvent *event) void BaseWindow::mouseReleaseEvent(QMouseEvent *event)
{ {
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
if (this->flags_ & FramelessDraggable) if (this->flags_.has(FramelessDraggable))
{ {
if (this->moving) if (this->moving)
{ {
@ -416,7 +415,7 @@ void BaseWindow::mouseReleaseEvent(QMouseEvent *event)
void BaseWindow::mouseMoveEvent(QMouseEvent *event) void BaseWindow::mouseMoveEvent(QMouseEvent *event)
{ {
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
if (this->flags_ & FramelessDraggable) if (this->flags_.has(FramelessDraggable))
{ {
if (this->moving) if (this->moving)
{ {
@ -515,7 +514,7 @@ void BaseWindow::resizeEvent(QResizeEvent *)
getApp()->windows->queueSave(); getApp()->windows->queueSave();
#endif #endif
this->moveIntoDesktopRect(this); //this->moveIntoDesktopRect(this);
this->calcButtonsSizes(); this->calcButtonsSizes();
} }
@ -535,6 +534,18 @@ void BaseWindow::closeEvent(QCloseEvent *)
this->closing.invoke(); this->closing.invoke();
} }
void BaseWindow::showEvent(QShowEvent *)
{
if (this->frameless_)
{
this->moveIntoDesktopRect(this);
qDebug() << "show";
QTimer::singleShot(30, this,
[this] { this->moveIntoDesktopRect(this); });
}
}
void BaseWindow::moveIntoDesktopRect(QWidget *parent) void BaseWindow::moveIntoDesktopRect(QWidget *parent)
{ {
if (!this->stayInScreenRect_) if (!this->stayInScreenRect_)
@ -643,7 +654,7 @@ void BaseWindow::paintEvent(QPaintEvent *)
void BaseWindow::updateScale() void BaseWindow::updateScale()
{ {
auto scale = auto scale =
this->nativeScale_ * (this->flags_ & DisableCustomScaling this->nativeScale_ * (this->flags_.has(DisableCustomScaling)
? 1 ? 1
: getABSettings()->getClampedUiScale()); : getABSettings()->getClampedUiScale());
@ -702,17 +713,11 @@ bool BaseWindow::handleDPICHANGED(MSG *msg)
float _scale = dpi / 96.f; float _scale = dpi / 96.f;
static bool firstResize = true;
if (!firstResize)
{
auto *prcNewWindow = reinterpret_cast<RECT *>(msg->lParam); auto *prcNewWindow = reinterpret_cast<RECT *>(msg->lParam);
SetWindowPos(msg->hwnd, nullptr, prcNewWindow->left, prcNewWindow->top, SetWindowPos(msg->hwnd, nullptr, prcNewWindow->left, prcNewWindow->top,
prcNewWindow->right - prcNewWindow->left, prcNewWindow->right - prcNewWindow->left,
prcNewWindow->bottom - prcNewWindow->top, prcNewWindow->bottom - prcNewWindow->top,
SWP_NOZORDER | SWP_NOACTIVATE); SWP_NOZORDER | SWP_NOACTIVATE);
}
firstResize = false;
this->nativeScale_ = _scale; this->nativeScale_ = _scale;
this->updateScale(); this->updateScale();
@ -809,8 +814,10 @@ bool BaseWindow::handleSIZE(MSG *msg)
{ {
this->ui_.windowLayout->setContentsMargins(0, 1, 0, 0); this->ui_.windowLayout->setContentsMargins(0, 1, 0, 0);
} }
if ((this->isNotMinimizedOrMaximized_ =
msg->wParam == SIZE_RESTORED)) this->isNotMinimizedOrMaximized_ = msg->wParam == SIZE_RESTORED;
if (this->isNotMinimizedOrMaximized_)
{ {
RECT rect; RECT rect;
::GetWindowRect(msg->hwnd, &rect); ::GetWindowRect(msg->hwnd, &rect);
@ -818,6 +825,7 @@ bool BaseWindow::handleSIZE(MSG *msg)
QRect(QPoint(rect.left, rect.top), QRect(QPoint(rect.left, rect.top),
QPoint(rect.right - 1, rect.bottom - 1)); QPoint(rect.right - 1, rect.bottom - 1));
} }
this->useNextBounds_.stop();
} }
} }
return false; return false;
@ -833,8 +841,10 @@ bool BaseWindow::handleMOVE(MSG *msg)
{ {
RECT rect; RECT rect;
::GetWindowRect(msg->hwnd, &rect); ::GetWindowRect(msg->hwnd, &rect);
this->currentBounds_ = QRect(QPoint(rect.left, rect.top), this->nextBounds_ = QRect(QPoint(rect.left, rect.top),
QPoint(rect.right - 1, rect.bottom - 1)); QPoint(rect.right - 1, rect.bottom - 1));
this->useNextBounds_.start(10);
} }
#endif #endif
return false; return false;
@ -942,7 +952,7 @@ bool BaseWindow::handleNCHITTEST(MSG *msg, long *result)
return true; return true;
} }
else if (this->flags_ & FramelessDraggable) else if (this->flags_.has(FramelessDraggable))
{ {
*result = 0; *result = 0;
bool client = false; bool client = false;

View file

@ -4,6 +4,7 @@
#include <functional> #include <functional>
#include <pajlada/signals/signalholder.hpp> #include <pajlada/signals/signalholder.hpp>
#include "common/FlagsEnum.hpp"
class QHBoxLayout; class QHBoxLayout;
struct tagMSG; struct tagMSG;
@ -32,7 +33,8 @@ public:
enum ActionOnFocusLoss { Nothing, Delete, Close, Hide }; enum ActionOnFocusLoss { Nothing, Delete, Close, Hide };
explicit BaseWindow(QWidget *parent = nullptr, Flags flags_ = None); explicit BaseWindow(FlagsEnum<Flags> flags_ = None,
QWidget *parent = nullptr);
void setInitialBounds(const QRect &bounds); void setInitialBounds(const QRect &bounds);
QRect getBounds(); QRect getBounds();
@ -54,8 +56,6 @@ public:
virtual float scale() const override; virtual float scale() const override;
float qtFontScale() const; float qtFontScale() const;
Flags getFlags();
pajlada::Signals::NoArgSignal closing; pajlada::Signals::NoArgSignal closing;
static bool supportsCustomWindowFrame(); static bool supportsCustomWindowFrame();
@ -72,6 +72,7 @@ protected:
virtual void resizeEvent(QResizeEvent *) override; virtual void resizeEvent(QResizeEvent *) override;
virtual void moveEvent(QMoveEvent *) override; virtual void moveEvent(QMoveEvent *) override;
virtual void closeEvent(QCloseEvent *) override; virtual void closeEvent(QCloseEvent *) override;
virtual void showEvent(QShowEvent *) override;
virtual void themeChangedEvent() override; virtual void themeChangedEvent() override;
virtual bool event(QEvent *event) override; virtual bool event(QEvent *event) override;
@ -106,7 +107,7 @@ private:
bool frameless_; bool frameless_;
bool stayInScreenRect_ = false; bool stayInScreenRect_ = false;
bool shown_ = false; bool shown_ = false;
Flags flags_; FlagsEnum<Flags> flags_;
float nativeScale_ = 1; float nativeScale_ = 1;
struct { struct {
@ -123,6 +124,8 @@ private:
#ifdef USEWINSDK #ifdef USEWINSDK
QRect initalBounds_; QRect initalBounds_;
QRect currentBounds_; QRect currentBounds_;
QRect nextBounds_;
QTimer useNextBounds_;
bool isNotMinimizedOrMaximized_{}; bool isNotMinimizedOrMaximized_{};
#endif #endif

View file

@ -21,7 +21,7 @@ TooltipWidget *TooltipWidget::getInstance()
} }
TooltipWidget::TooltipWidget(BaseWidget *parent) TooltipWidget::TooltipWidget(BaseWidget *parent)
: BaseWindow(parent, BaseWindow::TopMost) : BaseWindow(BaseWindow::TopMost, parent)
, displayImage_(new QLabel()) , displayImage_(new QLabel())
, displayText_(new QLabel()) , displayText_(new QLabel())
{ {

@ -1 +1 @@
Subproject commit 19cad9925f83d15d7487c16f0491f4741ec9f674 Subproject commit 1e0138c7ccedc6be859d28270ccd6195f235a94e

10
src/ForwardDecl.hpp Normal file
View file

@ -0,0 +1,10 @@
#pragma once
namespace chatterino {
class Channel;
class ChannelView;
using ChannelPtr = std::shared_ptr<Channel>;
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
} // namespace chatterino

View file

@ -1,3 +1,5 @@
#pragma once
#ifdef __cplusplus #ifdef __cplusplus
# include <fmt/format.h> # include <fmt/format.h>
# include <irccommand.h> # include <irccommand.h>

36
src/common/Modes.cpp Normal file
View file

@ -0,0 +1,36 @@
#include "Modes.hpp"
#include "util/CombinePath.hpp"
#include <QCoreApplication>
namespace chatterino {
Modes::Modes()
{
QFile file(combinePath(QCoreApplication::applicationDirPath(), "modes"));
file.open(QIODevice::ReadOnly);
while (!file.atEnd())
{
auto line = QString(file.readLine()).trimmed();
// we need to know if it is a nightly build to disable updates on windows
if (line == "nightly")
{
this->isNightly = true;
}
else if (line == "portable")
{
this->isPortable = true;
}
}
}
const Modes &Modes::getInstance()
{
static Modes instance;
return instance;
}
} // namespace chatterino

16
src/common/Modes.hpp Normal file
View file

@ -0,0 +1,16 @@
#pragma once
namespace chatterino {
class Modes
{
public:
Modes();
static const Modes &getInstance();
bool isNightly{};
bool isPortable{};
};
} // namespace chatterino

View file

@ -32,6 +32,10 @@ NetworkData::~NetworkData()
QString NetworkData::getHash() QString NetworkData::getHash()
{ {
static std::mutex mu;
std::lock_guard lock(mu);
if (this->hash_.isEmpty()) if (this->hash_.isEmpty())
{ {
QByteArray bytes; QByteArray bytes;
@ -242,7 +246,6 @@ void load(const std::shared_ptr<NetworkData> &data)
if (data->cache_) if (data->cache_)
{ {
QtConcurrent::run(loadCached, data); QtConcurrent::run(loadCached, data);
loadCached(data);
} }
else else
{ {

View file

@ -2,7 +2,7 @@
#include <QtGlobal> #include <QtGlobal>
#define CHATTERINO_VERSION "2.1.4-beta-1" #define CHATTERINO_VERSION "2.1.4"
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
# define CHATTERINO_OS "win" # define CHATTERINO_OS "win"

View file

@ -8,7 +8,7 @@ namespace chatterino {
// commandmodel // commandmodel
HighlightModel::HighlightModel(QObject *parent) HighlightModel::HighlightModel(QObject *parent)
: SignalVectorModel<HighlightPhrase>(4, parent) : SignalVectorModel<HighlightPhrase>(5, parent)
{ {
} }
@ -16,12 +16,13 @@ HighlightModel::HighlightModel(QObject *parent)
HighlightPhrase HighlightModel::getItemFromRow( HighlightPhrase HighlightModel::getItemFromRow(
std::vector<QStandardItem *> &row, const HighlightPhrase &original) std::vector<QStandardItem *> &row, const HighlightPhrase &original)
{ {
// key, alert, sound, regex // key, alert, sound, regex, case-sensitivity
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(), return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
row[1]->data(Qt::CheckStateRole).toBool(), row[1]->data(Qt::CheckStateRole).toBool(),
row[2]->data(Qt::CheckStateRole).toBool(), row[2]->data(Qt::CheckStateRole).toBool(),
row[3]->data(Qt::CheckStateRole).toBool()}; row[3]->data(Qt::CheckStateRole).toBool(),
row[4]->data(Qt::CheckStateRole).toBool()};
} }
// turns a row in the model into a vector item // turns a row in the model into a vector item
@ -32,6 +33,7 @@ void HighlightModel::getRowFromItem(const HighlightPhrase &item,
setBoolItem(row[1], item.getAlert()); setBoolItem(row[1], item.getAlert());
setBoolItem(row[2], item.getSound()); setBoolItem(row[2], item.getSound());
setBoolItem(row[3], item.isRegex()); setBoolItem(row[3], item.isRegex());
setBoolItem(row[4], item.isCaseSensitive());
} }
void HighlightModel::afterInit() void HighlightModel::afterInit()

View file

@ -15,21 +15,24 @@ public:
bool operator==(const HighlightPhrase &other) const bool operator==(const HighlightPhrase &other) const
{ {
return std::tie(this->pattern_, this->sound_, this->alert_, return std::tie(this->pattern_, this->sound_, this->alert_,
this->isRegex_) == std::tie(other.pattern_, this->isRegex_, this->caseSensitive_) ==
other.sound_, other.alert_, std::tie(other.pattern_, other.sound_, other.alert_,
other.isRegex_); other.isRegex_, other.caseSensitive_);
} }
HighlightPhrase(const QString &pattern, bool alert, bool sound, HighlightPhrase(const QString &pattern, bool alert, bool sound,
bool isRegex) bool isRegex, bool caseSensitive)
: pattern_(pattern) : pattern_(pattern)
, alert_(alert) , alert_(alert)
, sound_(sound) , sound_(sound)
, isRegex_(isRegex) , isRegex_(isRegex)
, regex_(isRegex_ ? pattern , caseSensitive_(caseSensitive)
, regex_(
isRegex_ ? pattern
: "\\b" + QRegularExpression::escape(pattern) + "\\b", : "\\b" + QRegularExpression::escape(pattern) + "\\b",
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption |
QRegularExpression::UseUnicodePropertiesOption) (caseSensitive_ ? QRegularExpression::NoPatternOption
: QRegularExpression::CaseInsensitiveOption))
{ {
} }
@ -60,11 +63,17 @@ public:
return this->isValid() && this->regex_.match(subject).hasMatch(); return this->isValid() && this->regex_.match(subject).hasMatch();
} }
bool isCaseSensitive() const
{
return this->caseSensitive_;
}
private: private:
QString pattern_; QString pattern_;
bool alert_; bool alert_;
bool sound_; bool sound_;
bool isRegex_; bool isRegex_;
bool caseSensitive_;
QRegularExpression regex_; QRegularExpression regex_;
}; };
} // namespace chatterino } // namespace chatterino
@ -82,6 +91,7 @@ struct Serialize<chatterino::HighlightPhrase> {
chatterino::rj::set(ret, "alert", value.getAlert(), a); chatterino::rj::set(ret, "alert", value.getAlert(), a);
chatterino::rj::set(ret, "sound", value.getSound(), a); chatterino::rj::set(ret, "sound", value.getSound(), a);
chatterino::rj::set(ret, "regex", value.isRegex(), a); chatterino::rj::set(ret, "regex", value.isRegex(), a);
chatterino::rj::set(ret, "case", value.isCaseSensitive(), a);
return ret; return ret;
} }
@ -93,20 +103,24 @@ struct Deserialize<chatterino::HighlightPhrase> {
{ {
if (!value.IsObject()) if (!value.IsObject())
{ {
return chatterino::HighlightPhrase(QString(), true, false, false); return chatterino::HighlightPhrase(QString(), true, false, false,
false);
} }
QString _pattern; QString _pattern;
bool _alert = true; bool _alert = true;
bool _sound = false; bool _sound = false;
bool _isRegex = false; bool _isRegex = false;
bool _caseSensitive = false;
chatterino::rj::getSafe(value, "pattern", _pattern); chatterino::rj::getSafe(value, "pattern", _pattern);
chatterino::rj::getSafe(value, "alert", _alert); chatterino::rj::getSafe(value, "alert", _alert);
chatterino::rj::getSafe(value, "sound", _sound); chatterino::rj::getSafe(value, "sound", _sound);
chatterino::rj::getSafe(value, "regex", _isRegex); chatterino::rj::getSafe(value, "regex", _isRegex);
chatterino::rj::getSafe(value, "case", _caseSensitive);
return chatterino::HighlightPhrase(_pattern, _alert, _sound, _isRegex); return chatterino::HighlightPhrase(_pattern, _alert, _sound,
_isRegex, _caseSensitive);
} }
}; };

View file

@ -8,7 +8,7 @@ namespace chatterino {
// commandmodel // commandmodel
UserHighlightModel::UserHighlightModel(QObject *parent) UserHighlightModel::UserHighlightModel(QObject *parent)
: SignalVectorModel<HighlightPhrase>(4, parent) : SignalVectorModel<HighlightPhrase>(5, parent)
{ {
} }
@ -21,7 +21,8 @@ HighlightPhrase UserHighlightModel::getItemFromRow(
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(), return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
row[1]->data(Qt::CheckStateRole).toBool(), row[1]->data(Qt::CheckStateRole).toBool(),
row[2]->data(Qt::CheckStateRole).toBool(), row[2]->data(Qt::CheckStateRole).toBool(),
row[3]->data(Qt::CheckStateRole).toBool()}; row[3]->data(Qt::CheckStateRole).toBool(),
row[4]->data(Qt::CheckStateRole).toBool()};
} }
// row into vector item // row into vector item
@ -32,6 +33,7 @@ void UserHighlightModel::getRowFromItem(const HighlightPhrase &item,
setBoolItem(row[1], item.getAlert()); setBoolItem(row[1], item.getAlert());
setBoolItem(row[2], item.getSound()); setBoolItem(row[2], item.getSound());
setBoolItem(row[3], item.isRegex()); setBoolItem(row[3], item.isRegex());
setBoolItem(row[4], item.isCaseSensitive());
} }
} // namespace chatterino } // namespace chatterino

View file

@ -22,7 +22,7 @@ public:
{ {
} }
std::size_t size() std::size_t size() const
{ {
return this->length_; return this->length_;
} }

View file

@ -145,7 +145,7 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
QSize(int(container.getScale() * image->width() * emoteScale), QSize(int(container.getScale() * image->width() * emoteScale),
int(container.getScale() * image->height() * emoteScale)); int(container.getScale() * image->height() * emoteScale));
container.addElement((new ImageLayoutElement(*this, image, size)) container.addElement(this->makeImageLayoutElement(image, size)
->setLink(this->getLink())); ->setLink(this->getLink()));
} }
else else
@ -159,6 +159,27 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
} }
} }
MessageLayoutElement *EmoteElement::makeImageLayoutElement(
const ImagePtr &image, const QSize &size)
{
return new ImageLayoutElement(*this, image, size);
}
// MOD BADGE
ModBadgeElement::ModBadgeElement(const EmotePtr &data,
MessageElementFlags flags_)
: EmoteElement(data, flags_)
{
}
MessageLayoutElement *ModBadgeElement::makeImageLayoutElement(
const ImagePtr &image, const QSize &size)
{
static const QColor modBadgeBackgroundColor("#34AE0A");
return new ImageWithBackgroundLayoutElement(*this, image, size,
modBadgeBackgroundColor);
}
// BADGE // BADGE
BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags) BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags)
: MessageElement(flags) : MessageElement(flags)

View file

@ -17,6 +17,7 @@
namespace chatterino { namespace chatterino {
class Channel; class Channel;
struct MessageLayoutContainer; struct MessageLayoutContainer;
class MessageLayoutElement;
class Image; class Image;
using ImagePtr = std::shared_ptr<Image>; using ImagePtr = std::shared_ptr<Image>;
@ -209,11 +210,26 @@ public:
MessageElementFlags flags_) override; MessageElementFlags flags_) override;
EmotePtr getEmote() const; EmotePtr getEmote() const;
protected:
virtual MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
const QSize &size);
private: private:
std::unique_ptr<TextElement> textElement_; std::unique_ptr<TextElement> textElement_;
EmotePtr emote_; EmotePtr emote_;
}; };
// Behaves like an emote element, except it creates a different image layout element that draws the mod badge background
class ModBadgeElement : public EmoteElement
{
public:
ModBadgeElement(const EmotePtr &data, MessageElementFlags flags_);
protected:
MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
const QSize &size) override;
};
class BadgeElement : public MessageElement class BadgeElement : public MessageElement
{ {
public: public:

View file

@ -168,6 +168,33 @@ int ImageLayoutElement::getXFromIndex(int index)
} }
} }
//
// IMAGE WITH BACKGROUND
//
ImageWithBackgroundLayoutElement::ImageWithBackgroundLayoutElement(
MessageElement &creator, ImagePtr image, const QSize &size, QColor color)
: ImageLayoutElement(creator, image, size)
, color_(color)
{
}
void ImageWithBackgroundLayoutElement::paint(QPainter &painter)
{
if (this->image_ == nullptr)
{
return;
}
auto pixmap = this->image_->pixmapOrLoad();
if (pixmap && !this->image_->animated())
{
painter.fillRect(QRectF(this->getRect()), this->color_);
// fourtf: make it use qreal values
painter.drawPixmap(QRectF(this->getRect()), *pixmap, QRectF());
}
}
// //
// TEXT // TEXT
// //

View file

@ -72,10 +72,22 @@ protected:
int getMouseOverIndex(const QPoint &abs) const override; int getMouseOverIndex(const QPoint &abs) const override;
int getXFromIndex(int index) override; int getXFromIndex(int index) override;
private:
ImagePtr image_; ImagePtr image_;
}; };
class ImageWithBackgroundLayoutElement : public ImageLayoutElement
{
public:
ImageWithBackgroundLayoutElement(MessageElement &creator, ImagePtr image,
const QSize &size, QColor color);
protected:
void paint(QPainter &painter) override;
private:
QColor color_;
};
// TEXT // TEXT
class TextLayoutElement : public MessageLayoutElement class TextLayoutElement : public MessageLayoutElement
{ {

View file

@ -0,0 +1,24 @@
#include "messages/search/AuthorPredicate.hpp"
namespace chatterino {
AuthorPredicate::AuthorPredicate(const QStringList &authors)
: authors_()
{
// Check if any comma-seperated values were passed and transform those
for (const auto &entry : authors)
{
for (const auto &author : entry.split(',', QString::SkipEmptyParts))
{
this->authors_ << author;
}
}
}
bool AuthorPredicate::appliesTo(const Message &message)
{
return authors_.contains(message.displayName, Qt::CaseInsensitive) ||
authors_.contains(message.loginName, Qt::CaseInsensitive);
}
} // namespace chatterino

View file

@ -0,0 +1,38 @@
#pragma once
#include "messages/search/MessagePredicate.hpp"
namespace chatterino {
/**
* @brief MessagePredicate checking for the author/sender of a message.
*
* This predicate will only allow messages that are sent by a list of users,
* specified by their user names.
*/
class AuthorPredicate : public MessagePredicate
{
public:
/**
* @brief Create an AuthorPredicate with a list of users to search for.
*
* @param authors a list of user names that a message should be sent from
*/
AuthorPredicate(const QStringList &authors);
/**
* @brief Checks whether the message is authored by any of the users passed
* in the constructor.
*
* @param message the message to check
* @return true if the message was authored by one of the specified users,
* false otherwise
*/
bool appliesTo(const Message &message);
private:
/// Holds the user names that will be searched for
QStringList authors_;
};
} // namespace chatterino

View file

@ -0,0 +1,22 @@
#include "messages/search/LinkPredicate.hpp"
#include "common/LinkParser.hpp"
namespace chatterino {
LinkPredicate::LinkPredicate()
{
}
bool LinkPredicate::appliesTo(const Message &message)
{
for (const auto &word :
message.messageText.split(' ', QString::SkipEmptyParts))
{
if (LinkParser(word).hasMatch())
return true;
}
return false;
}
} // namespace chatterino

View file

@ -0,0 +1,26 @@
#pragma once
#include "messages/search/MessagePredicate.hpp"
namespace chatterino {
/**
* @brief MessagePredicate checking whether a link exists in the message.
*
* This predicate will only allow messages that contain a link.
*/
class LinkPredicate : public MessagePredicate
{
public:
LinkPredicate();
/**
* @brief Checks whether the message contains a link.
*
* @param message the message to check
* @return true if the message contains a link, false otherwise
*/
bool appliesTo(const Message &message);
};
} // namespace chatterino

View file

@ -0,0 +1,31 @@
#pragma once
#include "messages/Message.hpp"
#include <memory>
namespace chatterino {
/**
* @brief Abstract base class for message predicates.
*
* Message predicates define certain features a message can satisfy.
* Features are represented by classes derived from this abstract class.
* A derived class must override `appliesTo` in order to test for the desired
* feature.
*/
class MessagePredicate
{
public:
/**
* @brief Checks whether this predicate applies to the passed message.
*
* Implementations of `appliesTo` should never change the message's content
* in order to be compatible with other MessagePredicates.
*
* @param message the message to check for this predicate
* @return true if this predicate applies, false otherwise
*/
virtual bool appliesTo(const Message &message) = 0;
};
} // namespace chatterino

View file

@ -0,0 +1,15 @@
#include "messages/search/SubstringPredicate.hpp"
namespace chatterino {
SubstringPredicate::SubstringPredicate(const QString &search)
: search_(search)
{
}
bool SubstringPredicate::appliesTo(const Message &message)
{
return message.searchText.contains(this->search_, Qt::CaseInsensitive);
}
} // namespace chatterino

View file

@ -0,0 +1,41 @@
#pragma once
#include "messages/search/MessagePredicate.hpp"
namespace chatterino {
/**
* @brief MessagePredicate checking whether a substring exists in the message.
*
* This predicate will only allow messages that contain a certain substring in
* their `searchText`.
*/
class SubstringPredicate : public MessagePredicate
{
public:
/**
* @brief Create a SubstringPredicate with a substring to search for.
*
* The passed string is searched for case-insensitively.
*
* @param search the string to search for in the message
*/
SubstringPredicate(const QString &search);
/**
* @brief Checks whether the message contains the substring passed in the
* constructor.
*
* The check is done case-insensitively.
*
* @param message the message to check
* @return true if the message contains the substring, false otherwise
*/
bool appliesTo(const Message &message);
private:
/// Holds the substring to search for in a message's `messageText`
const QString search_;
};
} // namespace chatterino

View file

@ -79,7 +79,38 @@ namespace {
return {Success, std::move(emotes)}; return {Success, std::move(emotes)};
} }
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
boost::optional<EmotePtr> parseModBadge(const QJsonObject &jsonRoot)
{
boost::optional<EmotePtr> modBadge;
auto room = jsonRoot.value("room").toObject();
auto modUrls = room.value("mod_urls").toObject();
if (!modUrls.isEmpty())
{
auto modBadge1x = getEmoteLink(modUrls, "1");
auto modBadge2x = getEmoteLink(modUrls, "2");
auto modBadge3x = getEmoteLink(modUrls, "4");
auto modBadgeImageSet = ImageSet{
Image::fromUrl(modBadge1x, 1),
modBadge2x.string.isEmpty() ? Image::getEmpty()
: Image::fromUrl(modBadge2x, 0.5),
modBadge3x.string.isEmpty() ? Image::getEmpty()
: Image::fromUrl(modBadge3x, 0.25),
};
modBadge = std::make_shared<Emote>(Emote{
{""},
modBadgeImageSet,
Tooltip{"Twitch Channel Moderator"},
modBadge1x,
});
}
return modBadge;
}
EmoteMap parseChannelEmotes(const QJsonObject &jsonRoot)
{ {
auto jsonSets = jsonRoot.value("sets").toObject(); auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = EmoteMap(); auto emotes = EmoteMap();
@ -110,7 +141,7 @@ namespace {
} }
} }
return {Success, std::move(emotes)}; return emotes;
} }
} // namespace } // namespace
@ -151,19 +182,26 @@ void FfzEmotes::loadEmotes()
.execute(); .execute();
} }
void FfzEmotes::loadChannel(const QString &channelId, void FfzEmotes::loadChannel(
std::function<void(EmoteMap &&)> callback) 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); log("[FFZEmotes] Reload FFZ Channel Emotes for channel {}\n", channelId);
NetworkRequest("https://api.frankerfacez.com/v1/room/id/" + channelId) NetworkRequest("https://api.frankerfacez.com/v1/room/id/" + channelId)
.timeout(20000) .timeout(20000)
.onSuccess([callback = std::move(callback)](auto result) -> Outcome { .onSuccess([emoteCallback = std::move(emoteCallback),
auto pair = parseChannelEmotes(result.parseJson()); modBadgeCallback =
if (pair.first) std::move(modBadgeCallback)](auto result) -> Outcome {
callback(std::move(pair.second)); auto json = result.parseJson();
return pair.first; auto emoteMap = parseChannelEmotes(json);
auto modBadge = parseModBadge(json);
emoteCallback(std::move(emoteMap));
modBadgeCallback(std::move(modBadge));
return Success;
}) })
.onError([channelId](int result) { .onError([channelId](int result) {
if (result == 203) if (result == 203)

View file

@ -22,8 +22,10 @@ public:
std::shared_ptr<const EmoteMap> emotes() const; std::shared_ptr<const EmoteMap> emotes() const;
boost::optional<EmotePtr> emote(const EmoteName &name) const; boost::optional<EmotePtr> emote(const EmoteName &name) const;
void loadEmotes(); void loadEmotes();
static void loadChannel(const QString &channelId, static void loadChannel(
std::function<void(EmoteMap &&)> callback); const QString &channelId,
std::function<void(EmoteMap &&)> emoteCallback,
std::function<void(boost::optional<EmotePtr>)> modBadgeCallback);
private: private:
Atomic<std::shared_ptr<const EmoteMap>> global_; Atomic<std::shared_ptr<const EmoteMap>> global_;

View file

@ -1,63 +0,0 @@
#include "FfzModBadge.hpp"
#include <QBuffer>
#include <QImageReader>
#include <QJsonObject>
#include <QPainter>
#include <QString>
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "messages/Emote.hpp"
namespace chatterino {
FfzModBadge::FfzModBadge(const QString &channelName)
: channelName_(channelName)
{
}
void FfzModBadge::loadCustomModBadge()
{
static QString partialUrl("https://cdn.frankerfacez.com/room-badge/mod/");
QString url = partialUrl + channelName_ + "/1";
NetworkRequest(url)
.onSuccess([this, url](auto result) -> Outcome {
auto data = result.getData();
QBuffer buffer(const_cast<QByteArray *>(&data));
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer);
if (reader.imageCount() == 0)
return Failure;
QPixmap badgeOverlay = QPixmap::fromImageReader(&reader);
QPixmap badgePixmap(18, 18);
// the default mod badge green color
badgePixmap.fill(QColor("#34AE0A"));
QPainter painter(&badgePixmap);
QRectF rect(0, 0, 18, 18);
painter.drawPixmap(rect, badgeOverlay, rect);
auto emote = Emote{{""},
ImageSet{Image::fromPixmap(badgePixmap)},
Tooltip{"Twitch Channel Moderator"},
Url{url}};
this->badge_ = std::make_shared<Emote>(emote);
// getBadge.execute();
return Success;
})
.execute();
}
EmotePtr FfzModBadge::badge() const
{
return this->badge_;
}
} // namespace chatterino

View file

@ -1,25 +0,0 @@
#pragma once
#include <QString>
#include <boost/optional.hpp>
namespace chatterino {
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class FfzModBadge
{
public:
FfzModBadge(const QString &channelName);
void loadCustomModBadge();
EmotePtr badge() const;
private:
const QString channelName_;
EmotePtr badge_;
};
} // namespace chatterino

View file

@ -48,8 +48,14 @@ void ChatroomChannel::refreshFFZChannelEmotes()
{ {
return; return;
} }
FfzEmotes::loadChannel(this->chatroomOwnerId, [this](auto &&emoteMap) { FfzEmotes::loadChannel(
this->ffzEmotes_.set(std::make_shared<EmoteMap>(std::move(emoteMap))); this->chatroomOwnerId,
[this](auto &&emoteMap) {
this->ffzEmotes_.set(
std::make_shared<EmoteMap>(std::move(emoteMap)));
},
[this](auto &&modBadge) {
this->ffzCustomModBadge_.set(std::move(modBadge));
}); });
} }

View file

@ -51,8 +51,8 @@ public:
private: private:
void addMessage(Communi::IrcMessage *message, const QString &target, void addMessage(Communi::IrcMessage *message, const QString &target,
const QString &content, TwitchIrcServer &server, bool isResub, const QString &content, TwitchIrcServer &server,
bool isAction); bool isResub, bool isAction);
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -88,7 +88,6 @@ TwitchChannel::TwitchChannel(const QString &name,
, globalFfz_(ffz) , globalFfz_(ffz)
, bttvEmotes_(std::make_shared<EmoteMap>()) , bttvEmotes_(std::make_shared<EmoteMap>())
, ffzEmotes_(std::make_shared<EmoteMap>()) , ffzEmotes_(std::make_shared<EmoteMap>())
, ffzCustomModBadge_(name)
, mod_(false) , mod_(false)
{ {
log("[TwitchChannel:{}] Opened", name); log("[TwitchChannel:{}] Opened", name);
@ -139,7 +138,6 @@ void TwitchChannel::initialize()
{ {
this->refreshChatters(); this->refreshChatters();
this->refreshBadges(); this->refreshBadges();
this->ffzCustomModBadge_.loadCustomModBadge();
} }
bool TwitchChannel::isEmpty() const bool TwitchChannel::isEmpty() const
@ -165,10 +163,17 @@ void TwitchChannel::refreshBTTVChannelEmotes()
void TwitchChannel::refreshFFZChannelEmotes() void TwitchChannel::refreshFFZChannelEmotes()
{ {
FfzEmotes::loadChannel( FfzEmotes::loadChannel(
this->roomId(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) { this->roomId(),
[this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
if (auto shared = weak.lock()) if (auto shared = weak.lock())
this->ffzEmotes_.set( this->ffzEmotes_.set(
std::make_shared<EmoteMap>(std::move(emoteMap))); std::make_shared<EmoteMap>(std::move(emoteMap)));
},
[this, weak = weakOf<Channel>(this)](auto &&modBadge) {
if (auto shared = weak.lock())
{
this->ffzCustomModBadge_.set(std::move(modBadge));
}
}); });
} }
@ -685,22 +690,23 @@ void TwitchChannel::refreshBadges()
void TwitchChannel::refreshCheerEmotes() void TwitchChannel::refreshCheerEmotes()
{ {
/*auto url = Url{"https://api.twitch.tv/kraken/bits/actions?channel_id=" + QString url("https://api.twitch.tv/kraken/bits/actions?channel_id=" +
this->getRoomId()}; this->roomId());
auto request = NetworkRequest::twitchRequest(url.string); NetworkRequest::twitchRequest(url)
request.setCaller(QThread::currentThread()); .onSuccess([this,
weak = weakOf<Channel>(this)](auto result) -> Outcome {
request.onSuccess(
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson()); auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
std::vector<CheerEmoteSet> emoteSets; std::vector<CheerEmoteSet> emoteSets;
for (auto &set : cheerEmoteSets) { for (auto &set : cheerEmoteSets)
{
auto cheerEmoteSet = CheerEmoteSet(); auto cheerEmoteSet = CheerEmoteSet();
cheerEmoteSet.regex = QRegularExpression( cheerEmoteSet.regex = QRegularExpression(
"^" + set.prefix.toLower() + "([1-9][0-9]*)$"); "^" + set.prefix + "([1-9][0-9]*)$",
QRegularExpression::CaseInsensitiveOption);
for (auto &tier : set.tiers) { for (auto &tier : set.tiers)
{
CheerEmote cheerEmote; CheerEmote cheerEmote;
cheerEmote.color = QColor(tier.color); cheerEmote.color = QColor(tier.color);
@ -733,7 +739,7 @@ void TwitchChannel::refreshCheerEmotes()
std::sort(cheerEmoteSet.cheerEmotes.begin(), std::sort(cheerEmoteSet.cheerEmotes.begin(),
cheerEmoteSet.cheerEmotes.end(), cheerEmoteSet.cheerEmotes.end(),
[](const auto &lhs, const auto &rhs) { [](const auto &lhs, const auto &rhs) {
return lhs.minBits < rhs.minBits; // return lhs.minBits > rhs.minBits;
}); });
emoteSets.emplace_back(cheerEmoteSet); emoteSets.emplace_back(cheerEmoteSet);
@ -741,10 +747,8 @@ void TwitchChannel::refreshCheerEmotes()
*this->cheerEmoteSets_.access() = std::move(emoteSets); *this->cheerEmoteSets_.access() = std::move(emoteSets);
return Success; return Success;
}); })
.execute();
request.execute();
*/
} }
boost::optional<EmotePtr> TwitchChannel::twitchBadge( boost::optional<EmotePtr> TwitchChannel::twitchBadge(
@ -765,9 +769,34 @@ boost::optional<EmotePtr> TwitchChannel::twitchBadge(
boost::optional<EmotePtr> TwitchChannel::ffzCustomModBadge() const boost::optional<EmotePtr> TwitchChannel::ffzCustomModBadge() const
{ {
if (auto badge = this->ffzCustomModBadge_.badge()) return this->ffzCustomModBadge_.get();
return badge; }
boost::optional<CheerEmote> TwitchChannel::cheerEmote(const QString &string)
{
auto sets = this->cheerEmoteSets_.access();
for (const auto &set : *sets)
{
auto match = set.regex.match(string);
if (!match.hasMatch())
{
continue;
}
QString amount = match.captured(1);
bool ok = false;
int bitAmount = amount.toInt(&ok);
if (!ok)
{
log("Error parsing bit amount in cheerEmote");
}
for (const auto &emote : set.cheerEmotes)
{
if (bitAmount >= emote.minBits)
{
return emote;
}
}
}
return boost::none; return boost::none;
} }

View file

@ -6,7 +6,7 @@
#include "common/ChannelChatters.hpp" #include "common/ChannelChatters.hpp"
#include "common/Outcome.hpp" #include "common/Outcome.hpp"
#include "common/UniqueAccess.hpp" #include "common/UniqueAccess.hpp"
#include "providers/ffz/FfzModBadge.hpp" #include "common/UsernameSet.hpp"
#include "providers/twitch/TwitchEmotes.hpp" #include "providers/twitch/TwitchEmotes.hpp"
#include <rapidjson/document.h> #include <rapidjson/document.h>
@ -96,6 +96,9 @@ public:
boost::optional<EmotePtr> twitchBadge(const QString &set, boost::optional<EmotePtr> twitchBadge(const QString &set,
const QString &version) const; const QString &version) const;
// Cheers
boost::optional<CheerEmote> cheerEmote(const QString &string);
// Signals // Signals
pajlada::Signals::NoArgSignal roomIdChanged; pajlada::Signals::NoArgSignal roomIdChanged;
pajlada::Signals::NoArgSignal userStateChanged; pajlada::Signals::NoArgSignal userStateChanged;
@ -145,13 +148,13 @@ protected:
FfzEmotes &globalFfz_; FfzEmotes &globalFfz_;
Atomic<std::shared_ptr<const EmoteMap>> bttvEmotes_; Atomic<std::shared_ptr<const EmoteMap>> bttvEmotes_;
Atomic<std::shared_ptr<const EmoteMap>> ffzEmotes_; Atomic<std::shared_ptr<const EmoteMap>> ffzEmotes_;
Atomic<boost::optional<EmotePtr>> ffzCustomModBadge_;
private: private:
// Badges // Badges
UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>> UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>>
badgeSets_; // "subscribers": { "0": ... "3": ... "6": ... badgeSets_; // "subscribers": { "0": ... "3": ... "6": ...
UniqueAccess<std::vector<CheerEmoteSet>> cheerEmoteSets_; UniqueAccess<std::vector<CheerEmoteSet>> cheerEmoteSets_;
FfzModBadge ffzCustomModBadge_;
bool mod_ = false; bool mod_ = false;
bool vip_ = false; bool vip_ = false;

View file

@ -143,9 +143,21 @@ bool TwitchMessageBuilder::isIgnored() const
return false; return false;
} }
void TwitchMessageBuilder::triggerHighlights() inline QMediaPlayer *getPlayer()
{
if (isGuiThread())
{ {
static auto player = new QMediaPlayer; static auto player = new QMediaPlayer;
return player;
}
else
{
return nullptr;
}
}
void TwitchMessageBuilder::triggerHighlights()
{
static QUrl currentPlayerUrl; static QUrl currentPlayerUrl;
if (this->historicalMessage_) if (this->historicalMessage_)
@ -164,6 +176,8 @@ void TwitchMessageBuilder::triggerHighlights()
bool resolveFocus = !hasFocus || getSettings()->highlightAlwaysPlaySound; bool resolveFocus = !hasFocus || getSettings()->highlightAlwaysPlaySound;
if (this->highlightSound_ && resolveFocus) if (this->highlightSound_ && resolveFocus)
{
if (auto player = getPlayer())
{ {
// update the media player url if necessary // update the media player url if necessary
QUrl highlightSoundUrl = QUrl highlightSoundUrl =
@ -181,6 +195,7 @@ void TwitchMessageBuilder::triggerHighlights()
player->play(); player->play();
} }
}
if (this->highlightAlert_) if (this->highlightAlert_)
{ {
@ -274,7 +289,7 @@ MessagePtr TwitchMessageBuilder::build()
if (iterator != this->tags.end()) if (iterator != this->tags.end())
{ {
this->hasBits_ = true; this->hasBits_ = true;
// bits = iterator.value().toString(); this->bits = iterator.value().toString();
} }
// twitch emotes // twitch emotes
@ -340,7 +355,7 @@ void TwitchMessageBuilder::addWords(
auto i = int(); auto i = int();
auto currentTwitchEmote = twitchEmotes.begin(); auto currentTwitchEmote = twitchEmotes.begin();
for (const auto &word : words) for (auto word : words)
{ {
// check if it's a twitch emote twitch emote // check if it's a twitch emote twitch emote
while (currentTwitchEmote != twitchEmotes.end() && while (currentTwitchEmote != twitchEmotes.end() &&
@ -361,10 +376,20 @@ void TwitchMessageBuilder::addWords(
MessageElementFlag::TwitchEmote); MessageElementFlag::TwitchEmote);
i += word.length() + 1; i += word.length() + 1;
int len = std::get<2>(*currentTwitchEmote).string.length();
currentTwitchEmote++; currentTwitchEmote++;
if (len < word.length())
{
word = word.mid(len);
this->message().elements.back()->setTrailingSpace(false);
}
else
{
continue; continue;
} }
}
// split words // split words
for (auto &variant : getApp()->emotes->emojis.parse(word)) for (auto &variant : getApp()->emotes->emojis.parse(word))
@ -952,7 +977,7 @@ void TwitchMessageBuilder::parseHighlights()
{ {
HighlightPhrase selfHighlight( HighlightPhrase selfHighlight(
currentUsername, getSettings()->enableSelfHighlightTaskbar, currentUsername, getSettings()->enableSelfHighlightTaskbar,
getSettings()->enableSelfHighlightSound, false); getSettings()->enableSelfHighlightSound, false, false);
activeHighlights.emplace_back(std::move(selfHighlight)); activeHighlights.emplace_back(std::move(selfHighlight));
} }
@ -1171,7 +1196,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
{ {
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge()) if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
{ {
this->emplace<BadgeElement>( this->emplace<ModBadgeElement>(
customModBadge.get(), customModBadge.get(),
MessageElementFlag::BadgeChannelAuthority) MessageElementFlag::BadgeChannelAuthority)
->setTooltip((*customModBadge)->tooltip.string); ->setTooltip((*customModBadge)->tooltip.string);
@ -1285,56 +1310,29 @@ void TwitchMessageBuilder::appendChatterinoBadges()
} }
} }
Outcome TwitchMessageBuilder::tryParseCheermote(const QString & /*string*/) Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
{
auto cheerOpt = this->twitchChannel->cheerEmote(string);
if (!cheerOpt)
{ {
// auto app = getApp();
//// Try to parse custom cheermotes
// const auto &channelResources = app->resources->channels[this->roomID_];
// if (channelResources.loaded) {
// for (const auto &cheermoteSet : channelResources.cheermoteSets) {
// auto match = cheermoteSet.regex.match(string);
// if (!match.hasMatch()) {
// continue;
// }
// QString amount = match.captured(1);
// bool ok = false;
// int numBits = amount.toInt(&ok);
// if (!ok) {
// Log("Error parsing bit amount in tryParseCheermote");
// return Failure;
// }
// auto savedIt = cheermoteSet.cheermotes.end();
// // Fetch cheermote that matches our numBits
// for (auto it = cheermoteSet.cheermotes.begin(); it !=
// cheermoteSet.cheermotes.end();
// ++it) {
// if (numBits >= it->minBits) {
// savedIt = it;
// } else {
// break;
// }
// }
// if (savedIt == cheermoteSet.cheermotes.end()) {
// Log("Error getting a cheermote from a cheermote set for the
// bit amount {}",
// numBits);
// return Failure;
// }
// const auto &cheermote = *savedIt;
// this->emplace<EmoteElement>(cheermote.animatedEmote,
// MessageElementFlag::BitsAnimated);
// this->emplace<TextElement>(amount, MessageElementFlag::Text,
// cheermote.color);
// return Success;
// }
//}
return Failure; return Failure;
} }
auto &cheerEmote = *cheerOpt;
if (cheerEmote.staticEmote)
{
this->emplace<EmoteElement>(cheerEmote.staticEmote,
MessageElementFlag::BitsStatic);
}
if (cheerEmote.animatedEmote)
{
this->emplace<EmoteElement>(cheerEmote.animatedEmote,
MessageElementFlag::BitsAnimated);
}
if (cheerEmote.color != QColor())
{
this->emplace<TextElement>(this->bits, MessageElementFlag::BitsAmount,
cheerEmote.color);
}
return Success;
}
} // namespace chatterino } // namespace chatterino

View file

@ -78,6 +78,7 @@ private:
QString roomID_; QString roomID_;
bool hasBits_ = false; bool hasBits_ = false;
QString bits;
bool historicalMessage_ = false; bool historicalMessage_ = false;
QString userId_; QString userId_;

View file

@ -8,6 +8,7 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <cassert> #include <cassert>
#include "common/Modes.hpp"
#include "util/CombinePath.hpp" #include "util/CombinePath.hpp"
namespace chatterino { namespace chatterino {
@ -32,7 +33,7 @@ bool Paths::createFolder(const QString &folderPath)
bool Paths::isPortable() bool Paths::isPortable()
{ {
return this->portable_.get(); return Modes::getInstance().isPortable;
} }
QString Paths::cacheDirectory() QString Paths::cacheDirectory()

View file

@ -208,6 +208,7 @@ public:
BoolSetting loadTwitchMessageHistoryOnConnect = { BoolSetting loadTwitchMessageHistoryOnConnect = {
"/misc/twitch/loadMessageHistoryOnConnect", true}; "/misc/twitch/loadMessageHistoryOnConnect", true};
IntSetting emotesTooltipPreview = {"/misc/emotesTooltipPreview", 0}; IntSetting emotesTooltipPreview = {"/misc/emotesTooltipPreview", 0};
BoolSetting openLinksIncognito = {"/misc/openLinksIncognito", 0};
QStringSetting cachePath = {"/cache/path", ""}; QStringSetting cachePath = {"/cache/path", ""};

View file

@ -1,6 +1,7 @@
#include "Updates.hpp" #include "Updates.hpp"
#include "Settings.hpp" #include "Settings.hpp"
#include "common/Modes.hpp"
#include "common/NetworkRequest.hpp" #include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp" #include "common/Outcome.hpp"
#include "common/Version.hpp" #include "common/Version.hpp"
@ -201,6 +202,14 @@ void Updates::installUpdates()
void Updates::checkForUpdates() void Updates::checkForUpdates()
{ {
// Disable updates if on nightly and windows.
#ifdef Q_OS_WIN
if (Modes::getInstance().isNightly)
{
return;
}
#endif
QString url = QString url =
"https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/" + "https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/" +
currentBranch(); currentBranch();

View file

@ -82,8 +82,7 @@ void WindowManager::showAccountSelectPopup(QPoint point)
w->refresh(); w->refresh();
QPoint buttonPos = point; QPoint buttonPos = point;
w->move(buttonPos.x(), buttonPos.y()); w->move(buttonPos.x() - 30, buttonPos.y());
w->show(); w->show();
w->setFocus(); w->setFocus();
} }

View file

@ -0,0 +1,64 @@
#pragma once
namespace chatterino {
std::vector<QString> getSampleCheerMessage()
{
// clang-format off
std::vector<QString> cheerMessageVector;
cheerMessageVector.push_back(R"(@badge-info=subscriber/4;badges=moderator/1,subscriber/3,sub-gifter/5;bits=2;color=#FF0000;display-name=69_faith_420;emotes=;flags=;id=c5fd49c7-ecbc-46dd-a790-c9f10fdaaa67;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567282184553;turbo=0;user-id=125608098;user-type=mod :69_faith_420!69_faith_420@69_faith_420.tmi.twitch.tv PRIVMSG #pajlada :cheer2 Stop what? I'm not doing anything.)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/4;badges=moderator/1,subscriber/3,sub-gifter/5;bits=2;color=#FF0000;display-name=69_faith_420;emotes=;flags=;id=397f4d2e-cac8-4689-922a-32709b9e8b4f;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567282159076;turbo=0;user-id=125608098;user-type=mod :69_faith_420!69_faith_420@69_faith_420.tmi.twitch.tv PRIVMSG #pajlada :cheer2 Who keeps getting their bits out now?)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=2;color=#FF0000;display-name=FlameGodFlann;emotes=;flags=;id=664ddc92-649d-4889-9641-208a6e62ef1e;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567282066199;turbo=0;user-id=56442185;user-type= :flamegodflann!flamegodflann@flamegodflann.tmi.twitch.tv PRIVMSG #pajlada :Cheer2 I'm saving my only can of Stella for your upcoming win, lets go!)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/3;badges=moderator/1,subscriber/3,bits/100;bits=10;color=#008000;display-name=k4izn;emotes=;flags=;id=3919af0b-93e0-412c-b238-d152f92ffea7;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567811485257;turbo=0;user-id=207114672;user-type=mod :k4izn!k4izn@k4izn.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Kleiner Cheer(s) !)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/12;badges=subscriber/12,bits/1000;bits=20;color=#00CCFF;display-name=YaBoiBurnsy;emotes=;flags=;id=5b53975d-b339-484f-a2a0-3ffbedde0df2;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567529634584;turbo=0;user-id=45258137;user-type= :yaboiburnsy!yaboiburnsy@yaboiburnsy.tmi.twitch.tv PRIVMSG #pajlada :ShowLove20)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=moderator/1,subscriber/0,bits-leader/2;bits=1;color=;display-name=jdfellie;emotes=;flags=18-22:A.3/P.5;id=28c8f4b7-b1e3-4404-b0f8-5cfe46411ef9;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567668177856;turbo=0;user-id=137619637;user-type=mod :jdfellie!jdfellie@jdfellie.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 take a bit bitch)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/2;bits=30;color=#EC3B83;display-name=Sammay;emotes=;flags=;id=ccf058a6-c1f1-45de-a764-fc8f96f21449;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566719874294;turbo=0;user-id=58283830;user-type= :sammay!sammay@sammay.tmi.twitch.tv PRIVMSG #pajlada :ShowLove30 @Emperor_Zhang)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/2;bits=6;color=#97E7FF;display-name=Emperor_Zhang;emotes=;flags=;id=53bab01b-9f6c-4123-a852-9916ab371cf9;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566719803345;turbo=0;user-id=105292882;user-type= :emperor_zhang!emperor_zhang@emperor_zhang.tmi.twitch.tv PRIVMSG #pajlada :uni6)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=5;color=#97E7FF;display-name=Emperor_Zhang;emotes=;flags=;id=545caec6-8b5f-460a-8b4b-3e407e179689;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566704926380;turbo=0;user-id=105292882;user-type= :emperor_zhang!emperor_zhang@emperor_zhang.tmi.twitch.tv PRIVMSG #pajlada :VoHiYo5)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=50;color=;display-name=Schmiddi55;emotes=;flags=;id=777f1018-941d-48aa-bf4e-ed8053d556c8;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567708393343;turbo=0;user-id=101444120;user-type= :schmiddi55!schmiddi55@schmiddi55.tmi.twitch.tv PRIVMSG #pajlada :cheer50 sere ihr radlertrinker)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/3;badges=subscriber/3,sub-gifter/10;bits=100;color=#0000FF;display-name=MLPTheChad;emotes=;flags=87-91:P.5;id=ed7db31e-884b-4761-9c88-b1676caa8814;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567681752733;turbo=0;user-id=63179867;user-type= :mlpthechad!mlpthechad@mlpthechad.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10 Statistically speaking, 10 out of 10 constipated people don't give a shit.)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/3;badges=subscriber/3,sub-gifter/10;bits=100;color=#0000FF;display-name=MLPTheChad;emotes=;flags=;id=506b482a-515a-4914-a694-2c69d2add23a;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567681618814;turbo=0;user-id=63179867;user-type= :mlpthechad!mlpthechad@mlpthechad.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10 That's some SUB par gameplay, Dabier.)");
cheerMessageVector.push_back(R"(@badge-info=;badges=premium/1;bits=100;color=;display-name=AkiraKurusu__;emotes=;flags=;id=6e343f5d-0e0e-47f7-bf6d-d5d7bf18b95a;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567765732657;turbo=0;user-id=151679027;user-type= :akirakurusu__!akirakurusu__@akirakurusu__.tmi.twitch.tv PRIVMSG #pajlada :TriHard100)");
cheerMessageVector.push_back(R"(@badge-info=;badges=premium/1;bits=1;color=;display-name=AkiraKurusu__;emotes=;flags=;id=dfdf6c2f-abee-4a4b-99fe-0d0b221f07de;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567765295301;turbo=0;user-id=151679027;user-type= :akirakurusu__!akirakurusu__@akirakurusu__.tmi.twitch.tv PRIVMSG #pajlada :TriHard1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=500;color=#0000FF;display-name=Stabbr;emotes=;flags=;id=e28b384e-fb6a-4da5-9a36-1b6153c6089d;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567648284623;turbo=0;user-id=183081176;user-type= :stabbr!stabbr@stabbr.tmi.twitch.tv PRIVMSG #pajlada :cheer500 Gotta be on top)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=subscriber/0,bits-leader/1;bits=100;color=;display-name=dbf_sub;emotes=;flags=;id=7cf317b8-6e28-4615-a0ba-e0bbaa0d4b29;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567646349560;turbo=0;user-id=450101746;user-type= :dbf_sub!dbf_sub@dbf_sub.tmi.twitch.tv PRIVMSG #pajlada :EleGiggle100)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=1;color=;display-name=dbf_sub;emotes=;flags=;id=43b5fc97-e7cc-4ac1-8d7e-7504c435c3f1;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567643510222;turbo=0;user-id=450101746;user-type= :dbf_sub!dbf_sub@dbf_sub.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/2;bits=100;color=;display-name=RobertsonRobotics;emotes=;flags=;id=598dfa14-23e9-4e45-a2fe-7a0263828817;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567873463820;turbo=0;user-id=117177721;user-type= :robertsonrobotics!robertsonrobotics@robertsonrobotics.tmi.twitch.tv PRIVMSG #pajlada :firstCheer100 This is so cool! Cant wait for the competition!)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=18;color=#1E90FF;display-name=Vipacman11;emotes=;flags=;id=07f59664-0c75-459e-b137-26c8d03e44be;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567873210379;turbo=0;user-id=89634839;user-type= :vipacman11!vipacman11@vipacman11.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=sub-gifter/5;bits=100;color=#FF7F50;display-name=darkside_sinner;emotes=;flags=;id=090102b3-369d-4ce4-ad1f-283849b10de0;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567822075293;turbo=0;user-id=104942909;user-type= :darkside_sinner!darkside_sinner@darkside_sinner.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10)");
cheerMessageVector.push_back(R"(@badge-info=;badges=sub-gifter/5;bits=200;color=#FF7F50;display-name=darkside_sinner;emotes=;flags=;id=2bdf7846-5ffa-4798-a397-997e7209a6d0;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567821695287;turbo=0;user-id=104942909;user-type= :darkside_sinner!darkside_sinner@darkside_sinner.tmi.twitch.tv PRIVMSG #pajlada :Subway200 bonus20)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=50;color=#0000FF;display-name=SincereBC;emotes=;flags=;id=b8c9236b-aeb9-4c72-a191-593e33c6c3f1;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567818308913;turbo=0;user-id=146097597;user-type= :sincerebc!sincerebc@sincerebc.tmi.twitch.tv PRIVMSG #pajlada :cheer50)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=1;color=#FF0000;display-name=AngryCh33s3puff;emotes=;flags=;id=6ab62185-ac1b-4ee5-bd93-165009917078;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567474810480;turbo=0;user-id=55399500;user-type= :angrych33s3puff!angrych33s3puff@angrych33s3puff.tmi.twitch.tv PRIVMSG #pajlada :cheer1 for the chair!)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/3;badges=moderator/1,subscriber/0,bits/1000;bits=1500;color=#5F9EA0;display-name=LaurenJW28;emotes=;flags=;id=2403678c-6109-43ac-b3b5-1f5230f91729;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567746107991;turbo=0;user-id=244354979;user-type=mod :laurenjw28!laurenjw28@laurenjw28.tmi.twitch.tv PRIVMSG #pajlada :Cheer1000 Cheer100 Cheer100 Cheer100 Cheer100 Cheer100)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=5;color=#5F9EA0;display-name=drkwings;emotes=;flags=;id=ad45dae5-b985-4526-9b9e-0bdba2d23289;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567742106689;turbo=0;user-id=440230526;user-type= :drkwings!drkwings@drkwings.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1 SeemsGood1 SeemsGood1 SeemsGood1 SeemsGood1)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/16;badges=subscriber/12,bits/1000;bits=1;color=;display-name=mustangbugatti;emotes=;flags=;id=ee987ee9-46a4-4c06-bf66-2cafff5d4cdd;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567883658780;turbo=0;user-id=115948494;user-type= :mustangbugatti!mustangbugatti@mustangbugatti.tmi.twitch.tv PRIVMSG #pajlada :(In clarkson accent) Some say...the only number in his contacts is himself..... And...that he is the international butt-dial champion... All we know is.... HES CALLED THE STIG Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/2;badges=subscriber/0,bits/1000;bits=1;color=;display-name=derpysaurus1;emotes=;flags=;id=c41c3d8b-c591-4db0-87e7-a78c5536de82;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567883655116;turbo=0;user-id=419221818;user-type= :derpysaurus1!derpysaurus1@derpysaurus1.tmi.twitch.tv PRIVMSG #pajlada :cheer1 OMG ur back yaaaaaaaaaaaaaaaaaaaaayyyyyyyyy)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/5;badges=subscriber/0,premium/1;bits=1;color=#8A2BE2;display-name=sirlordstallion;emotes=;flags=;id=61a87aeb-88b1-42f9-90f5-74429d8bf387;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567882978939;turbo=0;user-id=92145441;user-type= :sirlordstallion!sirlordstallion@sirlordstallion.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Alex is definetly not putting his eggs in Narreths basket)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=1;color=;display-name=xplosivegingerx;emotes=;flags=;id=f8aac1e0-050a-44bf-abcc-c0cf12cbedfc;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567882249072;turbo=0;user-id=151265906;user-type= :xplosivegingerx!xplosivegingerx@xplosivegingerx.tmi.twitch.tv PRIVMSG #pajlada :Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=500;color=;display-name=AlexJohanning;emotes=;flags=;id=4e4229a3-e7f2-4082-8c55-47d42db3b09c;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567881969862;turbo=0;user-id=190390930;user-type= :alexjohanning!alexjohanning@alexjohanning.tmi.twitch.tv PRIVMSG #pajlada :cheer500)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=245;color=;display-name=undonebunion6;emotes=;flags=;id=331ec583-0a80-4299-9206-0efd9e33d934;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567881553759;turbo=0;user-id=452974274;user-type= :undonebunion6!undonebunion6@undonebunion6.tmi.twitch.tv PRIVMSG #pajlada :cheer245 can I join?)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=100;color=;display-name=therealruffnix;emotes=;flags=61-67:S.6;id=25f567ad-ac95-45ab-b12e-4d647f6a2345;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567524218162;turbo=0;user-id=55059620;user-type= :therealruffnix!therealruffnix@therealruffnix.tmi.twitch.tv PRIVMSG #pajlada :cheer100 This is the kind of ASMR I'm missing on YouTube and PornHub)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=1;color=;display-name=BeamMeUpSnotty;emotes=;flags=;id=8022f41f-dcb8-42f2-b46a-04d4a99180bd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567270037926;turbo=0;user-id=261679182;user-type= :beammeupsnotty!beammeupsnotty@beammeupsnotty.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=10;color=#00FF7F;display-name=EXDE_HUN;emotes=;flags=;id=60d8835b-23fa-418c-96ca-5874e5d5e8ba;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566654664248;turbo=0;user-id=129793695;user-type= :exde_hun!exde_hun@exde_hun.tmi.twitch.tv PRIVMSG #pajlada :PogChamp10)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=fd6c5507-3a4e-4d24-8f6e-fadf07f520d3;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824273752;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=7003f119-b9a6-4319-a1e8-8e99f96ab01a;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824186437;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/3;bits=10;color=;display-name=slyckity;emotes=;flags=;id=3f7de686-77f6-46d2-919e-404312c6676f;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824128736;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/3;bits=10;color=;display-name=slyckity;emotes=;flags=;id=9e830ed3-8735-4ccb-9a8b-80466598ca19;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824118921;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=377;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=262f4d54-9b21-4f13-aac3-6d3b1051282f;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440897074;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :NotLikeThis377)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=144;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3556e0ad-b5f8-4190-9c4c-e39c1940d191;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440861545;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :bday144)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=89;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=96e380a5-786d-44b8-819a-529b6adb06ac;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440848361;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :SwiftRage89)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=34;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=76239011-65fa-4f6a-a6d6-dc5d5dcbd674;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440816630;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :MrDestructoid34)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=21;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=4c05c97c-7b6c-4ae9-bc91-04e98240c1d5;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440806389;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :TriHard21)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=8;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3b2ecce7-842e-429e-b6c8-9456c4646362;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440774009;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :EleGiggle8)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=5;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3b8736d1-832d-4152-832a-50c526714fd1;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440762580;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :uni5)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=3;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=c13a1540-2a03-4c7d-af50-cb20ed88cefd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440750103;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :Party3)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=2;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=5d889eeb-b6b9-4a4e-91ff-0aecdf297edd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440738337;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :ShowLove2)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=1;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=da47f91a-40d3-4209-ba1c-0219d8b8ecaf;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440720363;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :Scoops1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=10;color=#8A2BE2;display-name=EkimSky;emotes=;flags=;id=8adea5b4-7430-44ea-a666-5ebaceb69441;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567833047623;turbo=0;user-id=42132818;user-type= :ekimsky!ekimsky@ekimsky.tmi.twitch.tv PRIVMSG #pajlada :Hi Cheer10)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/2;bits=500;color=;display-name=godkiller76;emotes=;flags=;id=80e86bcc-d048-44f3-8073-9a1014568e0c;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567753685704;turbo=0;user-id=258838478;user-type= :godkiller76!godkiller76@godkiller76.tmi.twitch.tv PRIVMSG #pajlada :Party100 Party100 Party100 Party100 Party100)");
return cheerMessageVector;
// clang-format on
};
} // namespace chatterino

View file

@ -11,9 +11,8 @@
namespace chatterino { namespace chatterino {
AccountSwitchPopup::AccountSwitchPopup(QWidget *parent) AccountSwitchPopup::AccountSwitchPopup(QWidget *parent)
: QWidget(parent) : BaseWindow({BaseWindow::TopMost, BaseWindow::Frameless}, parent)
{ {
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
this->setWindowFlag(Qt::Popup); this->setWindowFlag(Qt::Popup);
#endif #endif
@ -25,8 +24,6 @@ AccountSwitchPopup::AccountSwitchPopup(QWidget *parent)
this->ui_.accountSwitchWidget->setFocusPolicy(Qt::NoFocus); this->ui_.accountSwitchWidget->setFocusPolicy(Qt::NoFocus);
vbox->addWidget(this->ui_.accountSwitchWidget); vbox->addWidget(this->ui_.accountSwitchWidget);
// vbox->setSizeConstraint(QLayout::SetMinimumSize);
auto hbox = new QHBoxLayout(); auto hbox = new QHBoxLayout();
auto manageAccountsButton = new QPushButton(this); auto manageAccountsButton = new QPushButton(this);
manageAccountsButton->setText("Manage Accounts"); manageAccountsButton->setText("Manage Accounts");
@ -38,9 +35,9 @@ AccountSwitchPopup::AccountSwitchPopup(QWidget *parent)
SettingsDialog::showDialog(SettingsDialogPreference::Accounts); // SettingsDialog::showDialog(SettingsDialogPreference::Accounts); //
}); });
this->setLayout(vbox); this->getLayoutContainer()->setLayout(vbox);
// this->setStyleSheet("background: #333"); this->setScaleIndependantSize(200, 200);
} }
void AccountSwitchPopup::refresh() void AccountSwitchPopup::refresh()

View file

@ -1,12 +1,13 @@
#pragma once #pragma once
#include "widgets/AccountSwitchWidget.hpp" #include "widgets/AccountSwitchWidget.hpp"
#include "widgets/BaseWindow.hpp"
#include <QWidget> #include <QWidget>
namespace chatterino { namespace chatterino {
class AccountSwitchPopup : public QWidget class AccountSwitchPopup : public BaseWindow
{ {
Q_OBJECT Q_OBJECT

View file

@ -2,6 +2,7 @@
#include "Application.hpp" #include "Application.hpp"
#include "common/Credentials.hpp" #include "common/Credentials.hpp"
#include "common/Modes.hpp"
#include "common/Version.hpp" #include "common/Version.hpp"
#include "controllers/accounts/AccountController.hpp" #include "controllers/accounts/AccountController.hpp"
#include "providers/twitch/TwitchIrcServer.hpp" #include "providers/twitch/TwitchIrcServer.hpp"
@ -23,6 +24,10 @@
#include "widgets/splits/Split.hpp" #include "widgets/splits/Split.hpp"
#include "widgets/splits/SplitContainer.hpp" #include "widgets/splits/SplitContainer.hpp"
#ifdef QT_DEBUG
# include "util/SampleCheerMessages.hpp"
#endif
#include <QApplication> #include <QApplication>
#include <QDesktopServices> #include <QDesktopServices>
#include <QHeaderView> #include <QHeaderView>
@ -36,7 +41,7 @@
namespace chatterino { namespace chatterino {
Window::Window(WindowType type) Window::Window(WindowType type)
: BaseWindow(nullptr, BaseWindow::EnableCustomFrame) : BaseWindow(BaseWindow::EnableCustomFrame)
, type_(type) , type_(type)
, notebook_(new SplitNotebook(this)) , notebook_(new SplitNotebook(this))
{ {
@ -112,18 +117,10 @@ bool Window::event(QEvent *event)
void Window::showEvent(QShowEvent *event) void Window::showEvent(QShowEvent *event)
{ {
// Startup notification // Startup notification
if (getSettings()->startUpNotification.getValue() < 1) /*if (getSettings()->startUpNotification.getValue() < 1)
{ {
getSettings()->startUpNotification = 1; getSettings()->startUpNotification = 1;
}*/
// auto box = new QMessageBox(
// QMessageBox::Information, "Chatterino 2 Beta",
// "Please note that this software is not stable yet. Things are "
// "rough "
// "around the edges and everything is subject to change.");
// box->setAttribute(Qt::WA_DeleteOnClose);
// box->show();
}
// Show changelog // Show changelog
if (getSettings()->currentVersion.getValue() != "" && if (getSettings()->currentVersion.getValue() != "" &&
@ -205,16 +202,9 @@ void Window::addDebugStuff()
{ {
#ifdef QT_DEBUG #ifdef QT_DEBUG
std::vector<QString> cheerMessages, subMessages, miscMessages; std::vector<QString> cheerMessages, subMessages, miscMessages;
cheerMessages = getSampleCheerMessage();
// clang-format off // clang-format off
cheerMessages.emplace_back(R"(@badges=subscriber/12,premium/1;bits=2000;color=#B22222;display-name=arzenhuz;emotes=185989:33-37;id=1ae336ac-8e1a-4d6b-8b00-9fcee26e8337;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1515783470139;turbo=0;user-id=111553331;user-type= :arzenhuz!arzenhuz@arzenhuz.tmi.twitch.tv PRIVMSG #pajlada :pajacheer2000 Buy pizza for both pajaH)");
cheerMessages.emplace_back(R"(@badges=subscriber/12,premium/1;bits=37;color=#3FBF72;display-name=VADIKUS007;emotes=;id=eedd95fd-2a17-4da1-879c-a1e76ffce582;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1515783184352;turbo=0;user-id=72256775;user-type= :vadikus007!vadikus007@vadikus007.tmi.twitch.tv PRIVMSG #pajlada :cheer37)");
cheerMessages.emplace_back(R"(@badges=moderator/1,subscriber/24,bits/100;bits=1;color=#DCD3E6;display-name=swiftapples;emotes=80803:7-13;id=1c4647f6-f1a8-4acc-a9b2-b5d23d91258d;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1515538318854;turbo=0;user-id=80526177;user-type=mod :swiftapples!swiftapples@swiftapples.tmi.twitch.tv PRIVMSG #pajlada :cheer1 pajaHey)");
cheerMessages.emplace_back(R"(@badges=subscriber/12,turbo/1;bits=1;color=#0A2927;display-name=Binkelderk;emotes=;id=a1d9bdc6-6f6a-4c03-8554-d5b34721a878;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1515538899479;turbo=1;user-id=89081828;user-type= :binkelderk!binkelderk@binkelderk.tmi.twitch.tv PRIVMSG #pajlada :pajacheer1)");
cheerMessages.emplace_back(R"(@badges=moderator/1,subscriber/24,bits/100;bits=1;color=#DCD3E6;display-name=swiftapples;emotes=80803:6-12;id=e9e21793-0b58-4ac6-8a1e-c19e165dbc9f;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1515539073209;turbo=0;user-id=80526177;user-type=mod :swiftapples!swiftapples@swiftapples.tmi.twitch.tv PRIVMSG #pajlada :bday1 pajaHey)");
cheerMessages.emplace_back(R"(@badges=partner/1;bits=1;color=#CC44FF;display-name=pajlada;emotes=;id=ca89214e-4fb5-48ec-853e-d2e6b41355ea;mod=0;room-id=39705480;subscriber=0;tmi-sent-ts=1515546977622;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #leesherwhy :test123 owocheer1 456test)");
cheerMessages.emplace_back(R"(@badges=subscriber/12,premium/1;bits=1;color=#3FBF72;display-name=VADIKUS007;emotes=;id=c4c5061b-f5c6-464b-8bff-7f1ac816caa7;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1515782817171;turbo=0;user-id=72256775;user-type= :vadikus007!vadikus007@vadikus007.tmi.twitch.tv PRIVMSG #pajlada :trihard1)");
cheerMessages.emplace_back(R"(@badges=;bits=1;color=#FF0000;display-name=?????;emotes=;id=979b6b4f-be9a-42fb-a54c-88fcb0aca18d;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1515782819084;turbo=0;user-id=70656218;user-type= :stels_tv!stels_tv@stels_tv.tmi.twitch.tv PRIVMSG #pajlada :trihard1)");
cheerMessages.emplace_back(R"(@badges=subscriber/3,premium/1;bits=1;color=#FF0000;display-name=kalvarenga;emotes=;id=4744d6f0-de1d-475d-a3ff-38647113265a;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1515782860740;turbo=0;user-id=108393131;user-type= :kalvarenga!kalvarenga@kalvarenga.tmi.twitch.tv PRIVMSG #pajlada :trihard1)");
subMessages.emplace_back(R"(@badges=staff/1,broadcaster/1,turbo/1;color=#008000;display-name=ronni;emotes=;id=db25007f-7a18-43eb-9379-80131e44d633;login=ronni;mod=0;msg-id=resub;msg-param-months=6;msg-param-sub-plan=Prime;msg-param-sub-plan-name=Prime;room-id=1337;subscriber=1;system-msg=ronni\shas\ssubscribed\sfor\s6\smonths!;tmi-sent-ts=1507246572675;turbo=1;user-id=1337;user-type=staff :tmi.twitch.tv USERNOTICE #pajlada :Great stream -- keep it up!)"); subMessages.emplace_back(R"(@badges=staff/1,broadcaster/1,turbo/1;color=#008000;display-name=ronni;emotes=;id=db25007f-7a18-43eb-9379-80131e44d633;login=ronni;mod=0;msg-id=resub;msg-param-months=6;msg-param-sub-plan=Prime;msg-param-sub-plan-name=Prime;room-id=1337;subscriber=1;system-msg=ronni\shas\ssubscribed\sfor\s6\smonths!;tmi-sent-ts=1507246572675;turbo=1;user-id=1337;user-type=staff :tmi.twitch.tv USERNOTICE #pajlada :Great stream -- keep it up!)");
subMessages.emplace_back(R"(@badges=staff/1,premium/1;color=#0000FF;display-name=TWW2;emotes=;id=e9176cd8-5e22-4684-ad40-ce53c2561c5e;login=tww2;mod=0;msg-id=subgift;msg-param-months=1;msg-param-recipient-display-name=Mr_Woodchuck;msg-param-recipient-id=89614178;msg-param-recipient-name=mr_woodchuck;msg-param-sub-plan-name=House\sof\sNyoro~n;msg-param-sub-plan=1000;room-id=19571752;subscriber=0;system-msg=TWW2\sgifted\sa\sTier\s1\ssub\sto\sMr_Woodchuck!;tmi-sent-ts=1521159445153;turbo=0;user-id=13405587;user-type=staff :tmi.twitch.tv USERNOTICE #pajlada)"); subMessages.emplace_back(R"(@badges=staff/1,premium/1;color=#0000FF;display-name=TWW2;emotes=;id=e9176cd8-5e22-4684-ad40-ce53c2561c5e;login=tww2;mod=0;msg-id=subgift;msg-param-months=1;msg-param-recipient-display-name=Mr_Woodchuck;msg-param-recipient-id=89614178;msg-param-recipient-name=mr_woodchuck;msg-param-sub-plan-name=House\sof\sNyoro~n;msg-param-sub-plan=1000;room-id=19571752;subscriber=0;system-msg=TWW2\sgifted\sa\sTier\s1\ssub\sto\sMr_Woodchuck!;tmi-sent-ts=1521159445153;turbo=0;user-id=13405587;user-type=staff :tmi.twitch.tv USERNOTICE #pajlada)");
@ -252,11 +242,19 @@ void Window::addDebugStuff()
app->twitch.server->addFakeMessage(msg); app->twitch.server->addFakeMessage(msg);
}); });
createWindowShortcut(this, "F7", [=] {
const auto &messages = cheerMessages;
static int index = 0;
const auto &msg = messages[index++ % messages.size()];
getApp()->twitch.server->addFakeMessage(msg);
});
createWindowShortcut(this, "F9", [=] { createWindowShortcut(this, "F9", [=] {
auto *dialog = new WelcomeDialog(); auto *dialog = new WelcomeDialog();
dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show(); dialog->show();
}); });
#endif #endif
} }
@ -373,16 +371,25 @@ void Window::onAccountSelected()
{ {
auto user = getApp()->accounts->twitch.getCurrent(); auto user = getApp()->accounts->twitch.getCurrent();
//#ifdef CHATTERINO_NIGHTLY_VERSION_STRING // update title
// auto windowTitleEnd = QString title = "Chatterino ";
// QString("Chatterino Nightly " CHATTERINO_VERSION if (Modes::getInstance().isNightly)
// " (" UGLYMACROHACK(CHATTERINO_NIGHTLY_VERSION_STRING) ")"); {
//#else title += "Nightly ";
auto windowTitleEnd = QString("Chatterino " CHATTERINO_VERSION); }
//#endif title += CHATTERINO_VERSION;
this->setWindowTitle(windowTitleEnd); if (Modes::getInstance().isNightly)
{
#ifdef CHATTERINO_NIGHTLY_VERSION_STRING
title +=
QString(" (" UGLYMACROHACK(CHATTERINO_NIGHTLY_VERSION_STRING) ")");
#endif
}
this->setWindowTitle(title);
// update user
if (user->isAnon()) if (user->isAnon())
{ {
if (this->userLabel_) if (this->userLabel_)
@ -397,6 +404,6 @@ void Window::onAccountSelected()
this->userLabel_->getLabel().setText(user->getUserName()); this->userLabel_->getLabel().setText(user->getUserName());
} }
} }
} } // namespace chatterino
} // namespace chatterino } // namespace chatterino

View file

@ -101,7 +101,7 @@ namespace {
} // namespace } // namespace
EmotePopup::EmotePopup(QWidget *parent) EmotePopup::EmotePopup(QWidget *parent)
: BaseWindow(parent, BaseWindow::EnableCustomFrame) : BaseWindow(BaseWindow::EnableCustomFrame, parent)
{ {
auto layout = new QVBoxLayout(this); auto layout = new QVBoxLayout(this);
this->getLayoutContainer()->setLayout(layout); this->getLayoutContainer()->setLayout(layout);

View file

@ -4,6 +4,7 @@
#include "common/Channel.hpp" #include "common/Channel.hpp"
#include "common/NetworkRequest.hpp" #include "common/NetworkRequest.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "messages/Message.hpp"
#include "providers/twitch/PartialTwitchUser.hpp" #include "providers/twitch/PartialTwitchUser.hpp"
#include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp" #include "providers/twitch/TwitchMessageBuilder.hpp"
@ -20,34 +21,30 @@ namespace chatterino {
LogsPopup::LogsPopup() LogsPopup::LogsPopup()
: channel_(Channel::getEmpty()) : channel_(Channel::getEmpty())
{ {
this->initLayout();
this->resize(400, 600); this->resize(400, 600);
} }
void LogsPopup::initLayout() void LogsPopup::setChannel(const ChannelPtr &channel)
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setMargin(0);
this->channelView_ = new ChannelView(this);
layout->addWidget(this->channelView_);
this->setLayout(layout);
}
void LogsPopup::setChannelName(QString channelName)
{
this->channelName_ = channelName;
}
void LogsPopup::setChannel(std::shared_ptr<Channel> channel)
{ {
this->channel_ = channel; this->channel_ = channel;
this->updateWindowTitle();
} }
void LogsPopup::setTargetUserName(QString userName) void LogsPopup::setChannelName(const QString &channelName)
{
this->channelName_ = channelName;
this->updateWindowTitle();
}
void LogsPopup::setTargetUserName(const QString &userName)
{ {
this->userName_ = userName; this->userName_ = userName;
this->updateWindowTitle();
}
void LogsPopup::updateWindowTitle()
{
this->setWindowTitle(this->userName_ + "'s logs in #" + this->channelName_);
} }
void LogsPopup::getLogs() void LogsPopup::getLogs()
@ -60,8 +57,6 @@ void LogsPopup::getLogs()
this->channelName_ = twitchChannel->getName(); this->channelName_ = twitchChannel->getName();
this->getLogviewerLogs(twitchChannel->roomId()); this->getLogviewerLogs(twitchChannel->roomId());
this->setWindowTitle(this->userName_ + "'s logs in #" +
this->channelName_);
return; return;
} }
} }
@ -83,7 +78,7 @@ void LogsPopup::setMessages(std::vector<MessagePtr> &messages)
ChannelPtr logsChannel(new Channel("logs", Channel::Type::Misc)); ChannelPtr logsChannel(new Channel("logs", Channel::Type::Misc));
logsChannel->addMessagesAtStart(messages); logsChannel->addMessagesAtStart(messages);
this->channelView_->setChannel(logsChannel); SearchPopup::setChannel(logsChannel);
} }
void LogsPopup::getLogviewerLogs(const QString &roomID) void LogsPopup::getLogviewerLogs(const QString &roomID)
@ -121,6 +116,8 @@ void LogsPopup::getLogviewerLogs(const QString &roomID)
static_cast<Communi::IrcPrivateMessage *>(ircMessage); static_cast<Communi::IrcPrivateMessage *>(ircMessage);
TwitchMessageBuilder builder(this->channel_.get(), privMsg, TwitchMessageBuilder builder(this->channel_.get(), privMsg,
args); args);
builder.message().searchText = message;
messages.push_back(builder.build()); messages.push_back(builder.build());
} }
@ -165,6 +162,7 @@ void LogsPopup::getOverrustleLogs()
for (auto i : dataMessages) for (auto i : dataMessages)
{ {
QJsonObject singleMessage = i.toObject(); QJsonObject singleMessage = i.toObject();
auto text = singleMessage.value("text").toString();
QTime timeStamp = QTime timeStamp =
QDateTime::fromSecsSinceEpoch( QDateTime::fromSecsSinceEpoch(
singleMessage.value("timestamp").toInt()) singleMessage.value("timestamp").toInt())
@ -175,9 +173,10 @@ void LogsPopup::getOverrustleLogs()
builder.emplace<TextElement>(this->userName_, builder.emplace<TextElement>(this->userName_,
MessageElementFlag::Username, MessageElementFlag::Username,
MessageColor::System); MessageColor::System);
builder.emplace<TextElement>( builder.emplace<TextElement>(text, MessageElementFlag::Text,
singleMessage.value("text").toString(), MessageColor::Text);
MessageElementFlag::Text, MessageColor::Text); builder.message().messageText = text;
builder.message().displayName = this->userName_;
messages.push_back(builder.release()); messages.push_back(builder.release());
} }
} }
@ -191,4 +190,5 @@ void LogsPopup::getOverrustleLogs()
}) })
.execute(); .execute();
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,37 +1,29 @@
#pragma once #pragma once
#include "widgets/BaseWindow.hpp" #include "widgets/helper/SearchPopup.hpp"
namespace chatterino { namespace chatterino {
class Channel; class LogsPopup : public SearchPopup
class ChannelView;
class Channel;
using ChannelPtr = std::shared_ptr<Channel>;
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
class LogsPopup : public BaseWindow
{ {
public: public:
LogsPopup(); LogsPopup();
void setChannelName(QString channelName); void setChannel(const ChannelPtr &channel) override;
void setChannel(std::shared_ptr<Channel> channel); void setChannelName(const QString &channelName);
void setTargetUserName(QString userName); void setTargetUserName(const QString &userName);
void getLogs(); void getLogs();
protected:
void updateWindowTitle() override;
private: private:
ChannelView *channelView_ = nullptr;
ChannelPtr channel_; ChannelPtr channel_;
QString userName_; QString userName_;
QString channelName_; QString channelName_;
void initLayout();
void setMessages(std::vector<MessagePtr> &messages); void setMessages(std::vector<MessagePtr> &messages);
void getOverrustleLogs(); void getOverrustleLogs();
void getLogviewerLogs(const QString &roomID); void getLogviewerLogs(const QString &roomID);

View file

@ -11,7 +11,7 @@
namespace chatterino { namespace chatterino {
NotificationPopup::NotificationPopup() NotificationPopup::NotificationPopup()
: BaseWindow((QWidget *)nullptr, BaseWindow::Frameless) : BaseWindow(BaseWindow::Frameless)
, channel_(std::make_shared<Channel>("notifications", Channel::Type::None)) , channel_(std::make_shared<Channel>("notifications", Channel::Type::None))
{ {

View file

@ -24,7 +24,7 @@ namespace chatterino {
SettingsDialog *SettingsDialog::handle = nullptr; SettingsDialog *SettingsDialog::handle = nullptr;
SettingsDialog::SettingsDialog() SettingsDialog::SettingsDialog()
: BaseWindow(nullptr, BaseWindow::DisableCustomScaling) : BaseWindow(BaseWindow::DisableCustomScaling)
{ {
this->setWindowTitle("Chatterino Settings"); this->setWindowTitle("Chatterino Settings");
@ -41,7 +41,7 @@ SettingsDialog::SettingsDialog()
void SettingsDialog::initUi() void SettingsDialog::initUi()
{ {
auto outerBox = LayoutCreator<SettingsDialog>(this) auto outerBox = LayoutCreator<QWidget>(this->getLayoutContainer())
.setLayoutType<QVBoxLayout>() .setLayoutType<QVBoxLayout>()
.withoutSpacing(); .withoutSpacing();

View file

@ -11,9 +11,8 @@
namespace chatterino { namespace chatterino {
UpdateDialog::UpdateDialog() UpdateDialog::UpdateDialog()
: BaseWindow(nullptr, : BaseWindow({BaseWindow::Frameless, BaseWindow::TopMost,
BaseWindow::Flags(BaseWindow::Frameless | BaseWindow::TopMost | BaseWindow::EnableCustomFrame})
BaseWindow::EnableCustomFrame))
{ {
auto layout = auto layout =
LayoutCreator<UpdateDialog>(this).setLayoutType<QVBoxLayout>(); LayoutCreator<UpdateDialog>(this).setLayoutType<QVBoxLayout>();

View file

@ -43,8 +43,7 @@ namespace {
} // namespace } // namespace
UserInfoPopup::UserInfoPopup() UserInfoPopup::UserInfoPopup()
: BaseWindow(nullptr, BaseWindow::Flags(BaseWindow::Frameless | : BaseWindow({BaseWindow::Frameless, BaseWindow::FramelessDraggable})
BaseWindow::FramelessDraggable))
, hack_(new bool) , hack_(new bool)
{ {
this->setStayInScreenRect(true); this->setStayInScreenRect(true);
@ -55,8 +54,8 @@ UserInfoPopup::UserInfoPopup()
auto app = getApp(); auto app = getApp();
auto layout = auto layout = LayoutCreator<QWidget>(this->getLayoutContainer())
LayoutCreator<UserInfoPopup>(this).setLayoutType<QVBoxLayout>(); .setLayoutType<QVBoxLayout>();
// first line // first line
auto head = layout.emplace<QHBoxLayout>().withoutMargin(); auto head = layout.emplace<QHBoxLayout>().withoutMargin();
@ -223,26 +222,32 @@ UserInfoPopup::UserInfoPopup()
}); });
} }
// this->setStyleSheet("font-size: 11pt;");
this->installEvents(); this->installEvents();
this->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Policy::Ignored);
} }
void UserInfoPopup::themeChangedEvent() void UserInfoPopup::themeChangedEvent()
{ {
BaseWindow::themeChangedEvent(); BaseWindow::themeChangedEvent();
this->setStyleSheet( for (auto &&child : this->findChildren<QCheckBox *>())
"background: #333; font-size: " + {
QString::number(getFonts() child->setFont(getFonts()->getFont(FontStyle::UiMedium, this->scale()));
->getFont(FontStyle::UiMediumBold, this->scale()) }
.pixelSize()) +
"px;");
} }
void UserInfoPopup::scaleChangedEvent(float /*scale*/) void UserInfoPopup::scaleChangedEvent(float /*scale*/)
{ {
themeChangedEvent(); themeChangedEvent();
QTimer::singleShot(20, this, [this] {
auto geo = this->geometry();
geo.setWidth(10);
geo.setHeight(10);
this->setGeometry(geo);
});
} }
void UserInfoPopup::installEvents() void UserInfoPopup::installEvents()

View file

@ -3,7 +3,7 @@
namespace chatterino { namespace chatterino {
WelcomeDialog::WelcomeDialog() WelcomeDialog::WelcomeDialog()
: BaseWindow(nullptr, BaseWindow::EnableCustomFrame) : BaseWindow(BaseWindow::EnableCustomFrame)
{ {
this->setWindowTitle("Chatterino quick setup"); this->setWindowTitle("Chatterino quick setup");
} }

View file

@ -133,6 +133,8 @@ ChannelView::ChannelView(BaseWidget *parent)
this->clickTimer_ = new QTimer(this); this->clickTimer_ = new QTimer(this);
this->clickTimer_->setSingleShot(true); this->clickTimer_->setSingleShot(true);
this->clickTimer_->setInterval(500); this->clickTimer_->setInterval(500);
this->setFocusPolicy(Qt::FocusPolicy::StrongFocus);
} }
void ChannelView::initializeLayout() void ChannelView::initializeLayout()
@ -1526,14 +1528,8 @@ void ChannelView::addContextMenuItems(
QString url = hoveredElement->getLink().value; QString url = hoveredElement->getLink().value;
// open link // open link
bool incognitoByDefault = supportsIncognitoLinks() && menu->addAction("Open link",
layout->getMessage()->loginName == "hemirt"; [url] { QDesktopServices::openUrl(QUrl(url)); });
menu->addAction("Open link", [url, incognitoByDefault] {
if (incognitoByDefault)
openLinkIncognito(url);
else
QDesktopServices::openUrl(QUrl(url));
});
// open link default // open link default
if (supportsIncognitoLinks()) if (supportsIncognitoLinks())
{ {
@ -1699,6 +1695,9 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
case Link::Url: case Link::Url:
{ {
if (getSettings()->openLinksIncognito && supportsIncognitoLinks())
openLinkIncognito(link.value);
else
QDesktopServices::openUrl(QUrl(link.value)); QDesktopServices::openUrl(QUrl(link.value));
} }
break; break;

View file

@ -7,23 +7,70 @@
#include "common/Channel.hpp" #include "common/Channel.hpp"
#include "messages/Message.hpp" #include "messages/Message.hpp"
#include "messages/search/AuthorPredicate.hpp"
#include "messages/search/LinkPredicate.hpp"
#include "messages/search/SubstringPredicate.hpp"
#include "widgets/helper/ChannelView.hpp" #include "widgets/helper/ChannelView.hpp"
namespace chatterino { namespace chatterino {
ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName,
const LimitedQueueSnapshot<MessagePtr> &snapshot)
{
ChannelPtr channel(new Channel(channelName, Channel::Type::None));
// Parse predicates from tags in "text"
auto predicates = parsePredicates(text);
// Check for every message whether it fulfills all predicates that have
// been registered
for (size_t i = 0; i < snapshot.size(); ++i)
{
MessagePtr message = snapshot[i];
bool accept = true;
for (const auto &pred : predicates)
{
// Discard the message as soon as one predicate fails
if (!pred->appliesTo(*message))
{
accept = false;
break;
}
}
// If all predicates match, add the message to the channel
if (accept)
channel->addMessage(message);
}
return channel;
}
SearchPopup::SearchPopup() SearchPopup::SearchPopup()
{ {
this->initLayout(); this->initLayout();
this->resize(400, 600); this->resize(400, 600);
} }
void SearchPopup::setChannel(ChannelPtr channel) void SearchPopup::setChannel(const ChannelPtr &channel)
{ {
this->channelName_ = channel->getName(); this->channelName_ = channel->getName();
this->snapshot_ = channel->getMessageSnapshot(); this->snapshot_ = channel->getMessageSnapshot();
this->performSearch(); this->search();
this->setWindowTitle("Searching in " + channel->getName() + "s history"); this->updateWindowTitle();
}
void SearchPopup::updateWindowTitle()
{
this->setWindowTitle("Searching in " + this->channelName_ + "s history");
}
void SearchPopup::search()
{
this->channelView_->setChannel(filter(this->searchInput_->text(),
this->channelName_, this->snapshot_));
} }
void SearchPopup::keyPressEvent(QKeyEvent *e) void SearchPopup::keyPressEvent(QKeyEvent *e)
@ -43,18 +90,20 @@ void SearchPopup::initLayout()
{ {
QVBoxLayout *layout1 = new QVBoxLayout(this); QVBoxLayout *layout1 = new QVBoxLayout(this);
layout1->setMargin(0); layout1->setMargin(0);
layout1->setSpacing(0);
// HBOX // HBOX
{ {
QHBoxLayout *layout2 = new QHBoxLayout(this); QHBoxLayout *layout2 = new QHBoxLayout(this);
layout2->setMargin(6); layout2->setMargin(8);
layout2->setSpacing(8);
// SEARCH INPUT // SEARCH INPUT
{ {
this->searchInput_ = new QLineEdit(this); this->searchInput_ = new QLineEdit(this);
layout2->addWidget(this->searchInput_); layout2->addWidget(this->searchInput_);
QObject::connect(this->searchInput_, &QLineEdit::returnPressed, QObject::connect(this->searchInput_, &QLineEdit::returnPressed,
[this] { this->performSearch(); }); [this] { this->search(); });
} }
// SEARCH BUTTON // SEARCH BUTTON
@ -63,7 +112,7 @@ void SearchPopup::initLayout()
searchButton->setText("Search"); searchButton->setText("Search");
layout2->addWidget(searchButton); layout2->addWidget(searchButton);
QObject::connect(searchButton, &QPushButton::clicked, QObject::connect(searchButton, &QPushButton::clicked,
[this] { this->performSearch(); }); [this] { this->search(); });
} }
layout1->addLayout(layout2); layout1->addLayout(layout2);
@ -80,25 +129,55 @@ void SearchPopup::initLayout()
} }
} }
void SearchPopup::performSearch() std::vector<std::unique_ptr<MessagePredicate>> SearchPopup::parsePredicates(
const QString &input)
{ {
QString text = searchInput_->text(); static QRegularExpression predicateRegex(R"(^(\w+):([\w,]+)$)");
ChannelPtr channel(new Channel(this->channelName_, Channel::Type::None)); auto predicates = std::vector<std::unique_ptr<MessagePredicate>>();
auto words = input.split(' ', QString::SkipEmptyParts);
auto authors = QStringList();
for (size_t i = 0; i < this->snapshot_.size(); i++) for (auto it = words.begin(); it != words.end();)
{ {
MessagePtr message = this->snapshot_[i]; if (auto match = predicateRegex.match(*it); match.hasMatch())
if (text.isEmpty() ||
message->searchText.indexOf(this->searchInput_->text(), 0,
Qt::CaseInsensitive) != -1)
{ {
channel->addMessage(message); QString name = match.captured(1);
QString value = match.captured(2);
bool remove = true;
// match predicates
if (name == "from")
{
authors.append(value);
}
else if (name == "has" && value == "link")
{
predicates.push_back(std::make_unique<LinkPredicate>());
}
else
{
remove = false;
}
// remove or advance
it = remove ? words.erase(it) : ++it;
}
else
{
++it;
} }
} }
this->channelView_->setChannel(channel); if (!authors.empty())
predicates.push_back(std::make_unique<AuthorPredicate>(authors));
if (!words.empty())
predicates.push_back(
std::make_unique<SubstringPredicate>(words.join(" ")));
return predicates;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,6 +1,8 @@
#pragma once #pragma once
#include "ForwardDecl.hpp"
#include "messages/LimitedQueueSnapshot.hpp" #include "messages/LimitedQueueSnapshot.hpp"
#include "messages/search/MessagePredicate.hpp"
#include "widgets/BaseWindow.hpp" #include "widgets/BaseWindow.hpp"
#include <memory> #include <memory>
@ -9,30 +11,50 @@ class QLineEdit;
namespace chatterino { namespace chatterino {
class Channel;
class ChannelView;
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
class SearchPopup : public BaseWindow class SearchPopup : public BaseWindow
{ {
public: public:
SearchPopup(); SearchPopup();
void setChannel(std::shared_ptr<Channel> channel); virtual void setChannel(const ChannelPtr &channel);
protected: protected:
void keyPressEvent(QKeyEvent *e) override; void keyPressEvent(QKeyEvent *e) override;
virtual void updateWindowTitle();
private: private:
void initLayout(); void initLayout();
void performSearch(); void search();
/**
* @brief Only retains those message from a list of messages that satisfy a
* search query.
*
* @param text the search query -- will be parsed for MessagePredicates
* @param channelName name of the channel to be returned
* @param snapshot list of messages to filter
*
* @return a ChannelPtr with "channelName" and the filtered messages from
* "snapshot"
*/
static ChannelPtr filter(const QString &text, const QString &channelName,
const LimitedQueueSnapshot<MessagePtr> &snapshot);
/**
* @brief Checks the input for tags and registers their corresponding
* predicates.
*
* @param input the string to check for tags
* @return a vector of MessagePredicates requested in the input
*/
static std::vector<std::unique_ptr<MessagePredicate>> parsePredicates(
const QString &input);
LimitedQueueSnapshot<MessagePtr> snapshot_; LimitedQueueSnapshot<MessagePtr> snapshot_;
QLineEdit *searchInput_; QLineEdit *searchInput_{};
ChannelView *channelView_; ChannelView *channelView_{};
QString channelName_; QString channelName_{};
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -11,6 +11,7 @@
#include "singletons/WindowManager.hpp" #include "singletons/WindowManager.hpp"
#include "util/FuzzyConvert.hpp" #include "util/FuzzyConvert.hpp"
#include "util/Helpers.hpp" #include "util/Helpers.hpp"
#include "util/IncognitoBrowser.hpp"
#include "widgets/BaseWindow.hpp" #include "widgets/BaseWindow.hpp"
#include "widgets/helper/Line.hpp" #include "widgets/helper/Line.hpp"
@ -377,6 +378,12 @@ void GeneralPage::initLayout(SettingsLayout &layout)
layout.addTitle("Miscellaneous"); layout.addTitle("Miscellaneous");
if (supportsIncognitoLinks())
{
layout.addCheckbox("Open links in incognito/private mode",
s.openLinksIncognito);
}
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
if (!getPaths()->isPortable()) if (!getPaths()->isPortable())
{ {

View file

@ -57,7 +57,7 @@ HighlightingPage::HighlightingPage()
view->addRegexHelpLink(); view->addRegexHelpLink();
view->setTitles({"Pattern", "Flash\ntaskbar", "Play\nsound", view->setTitles({"Pattern", "Flash\ntaskbar", "Play\nsound",
"Enable\nregex"}); "Enable\nregex", "Case-\nsensitive"});
view->getTableView()->horizontalHeader()->setSectionResizeMode( view->getTableView()->horizontalHeader()->setSectionResizeMode(
QHeaderView::Fixed); QHeaderView::Fixed);
view->getTableView()->horizontalHeader()->setSectionResizeMode( view->getTableView()->horizontalHeader()->setSectionResizeMode(
@ -71,8 +71,8 @@ HighlightingPage::HighlightingPage()
}); });
view->addButtonPressed.connect([] { view->addButtonPressed.connect([] {
getApp()->highlights->phrases.appendItem( getApp()->highlights->phrases.appendItem(HighlightPhrase{
HighlightPhrase{"my phrase", true, false, false}); "my phrase", true, false, false, false});
}); });
} }
@ -87,6 +87,10 @@ HighlightingPage::HighlightingPage()
.getElement(); .getElement();
view->addRegexHelpLink(); view->addRegexHelpLink();
view->getTableView()->horizontalHeader()->hideSection(4);
// Case-sensitivity doesn't make sense for user names so it is
// set to "false" by default & no checkbox is shown
view->setTitles({"Username", "Flash\ntaskbar", "Play\nsound", view->setTitles({"Username", "Flash\ntaskbar", "Play\nsound",
"Enable\nregex"}); "Enable\nregex"});
view->getTableView()->horizontalHeader()->setSectionResizeMode( view->getTableView()->horizontalHeader()->setSectionResizeMode(
@ -103,7 +107,7 @@ HighlightingPage::HighlightingPage()
view->addButtonPressed.connect([] { view->addButtonPressed.connect([] {
getApp()->highlights->highlightedUsers.appendItem( getApp()->highlights->highlightedUsers.appendItem(
HighlightPhrase{"highlighted user", true, false, HighlightPhrase{"highlighted user", true, false, false,
false}); false});
}); });
} }

View file

@ -52,7 +52,7 @@ namespace {
const QString &title, const QString &description) const QString &title, const QString &description)
{ {
auto window = auto window =
new BaseWindow(parent, BaseWindow::Flags::EnableCustomFrame); new BaseWindow(BaseWindow::Flags::EnableCustomFrame, parent);
window->setWindowTitle("Chatterino - " + title); window->setWindowTitle("Chatterino - " + title);
window->setAttribute(Qt::WA_DeleteOnClose); window->setAttribute(Qt::WA_DeleteOnClose);
auto layout = new QVBoxLayout(); auto layout = new QVBoxLayout();
@ -89,6 +89,7 @@ Split::Split(QWidget *parent)
{ {
this->setMouseTracking(true); this->setMouseTracking(true);
this->view_->setPausable(true); this->view_->setPausable(true);
this->view_->setFocusPolicy(Qt::FocusPolicy::NoFocus);
this->vbox_->setSpacing(0); this->vbox_->setSpacing(0);
this->vbox_->setMargin(1); this->vbox_->setMargin(1);