mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Merge branch 'master' into irc-support
This commit is contained in:
commit
3ab7362304
204 changed files with 17026 additions and 16467 deletions
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Normalize line endings to LF for files that Git detects as text
|
||||
* text=auto
|
7
.gitmodules
vendored
7
.gitmodules
vendored
|
@ -4,10 +4,6 @@
|
|||
[submodule "lib/humanize"]
|
||||
path = lib/humanize
|
||||
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"]
|
||||
path = lib/qBreakpad
|
||||
url = https://github.com/jiakuan/qBreakpad.git
|
||||
|
@ -29,3 +25,6 @@
|
|||
[submodule "lib/qtkeychain"]
|
||||
path = lib/qtkeychain
|
||||
url = https://github.com/Chatterino/qtkeychain
|
||||
[submodule "lib/websocketpp"]
|
||||
path = lib/websocketpp
|
||||
url = https://github.com/ziocleto/websocketpp
|
||||
|
|
|
@ -40,6 +40,8 @@ build_script:
|
|||
|
||||
cp release/chatterino.exe Chatterino2/
|
||||
|
||||
echo nightly > Chatterino2/modes
|
||||
|
||||
7z a chatterino-windows-x86-64.zip Chatterino2/
|
||||
artifacts:
|
||||
- path: build/chatterino-windows-x86-64.zip
|
||||
|
@ -48,7 +50,7 @@ deploy:
|
|||
- provider: GitHub
|
||||
tag: 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:
|
||||
secure: sAJzAbiQSsYZLT+byDar9u61X0E9o35anaPMSFkOzdHeDFHjx1kW4cDP/4EEbxhx
|
||||
repository: Chatterino/chatterino2
|
||||
|
|
|
@ -83,6 +83,7 @@ SOURCES += \
|
|||
src/common/DownloadManager.cpp \
|
||||
src/common/Env.cpp \
|
||||
src/common/LinkParser.cpp \
|
||||
src/common/Modes.cpp \
|
||||
src/common/NetworkManager.cpp \
|
||||
src/common/NetworkPrivate.cpp \
|
||||
src/common/NetworkRequest.cpp \
|
||||
|
@ -123,12 +124,14 @@ SOURCES += \
|
|||
src/messages/MessageColor.cpp \
|
||||
src/messages/MessageContainer.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/LoadBttvChannelEmote.cpp \
|
||||
src/providers/chatterino/ChatterinoBadges.cpp \
|
||||
src/providers/emoji/Emojis.cpp \
|
||||
src/providers/ffz/FfzEmotes.cpp \
|
||||
src/providers/ffz/FfzModBadge.cpp \
|
||||
src/providers/irc/AbstractIrcServer.cpp \
|
||||
src/providers/irc/Irc2.cpp \
|
||||
src/providers/irc/IrcAccount.cpp \
|
||||
|
@ -239,6 +242,7 @@ HEADERS += \
|
|||
src/common/DownloadManager.hpp \
|
||||
src/common/Env.hpp \
|
||||
src/common/LinkParser.hpp \
|
||||
src/common/Modes.hpp \
|
||||
src/common/NetworkCommon.hpp \
|
||||
src/common/NetworkManager.hpp \
|
||||
src/common/NetworkPrivate.hpp \
|
||||
|
@ -276,6 +280,7 @@ HEADERS += \
|
|||
src/controllers/taggedusers/TaggedUser.hpp \
|
||||
src/controllers/taggedusers/TaggedUsersController.hpp \
|
||||
src/controllers/taggedusers/TaggedUsersModel.hpp \
|
||||
src/ForwardDecl.hpp \
|
||||
src/messages/Emote.hpp \
|
||||
src/messages/Image.hpp \
|
||||
src/messages/ImageSet.hpp \
|
||||
|
@ -291,6 +296,10 @@ HEADERS += \
|
|||
src/messages/MessageContainer.hpp \
|
||||
src/messages/MessageElement.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/PrecompiledHeader.hpp \
|
||||
src/providers/bttv/BttvEmotes.hpp \
|
||||
|
@ -298,7 +307,6 @@ HEADERS += \
|
|||
src/providers/chatterino/ChatterinoBadges.hpp \
|
||||
src/providers/emoji/Emojis.hpp \
|
||||
src/providers/ffz/FfzEmotes.hpp \
|
||||
src/providers/ffz/FfzModBadge.hpp \
|
||||
src/providers/irc/AbstractIrcServer.hpp \
|
||||
src/providers/irc/Irc2.hpp \
|
||||
src/providers/irc/IrcAccount.hpp \
|
||||
|
@ -356,6 +364,7 @@ HEADERS += \
|
|||
src/util/rangealgorithm.hpp \
|
||||
src/util/RapidjsonHelpers.hpp \
|
||||
src/util/RemoveScrollAreaBackground.hpp \
|
||||
src/util/SampleCheerMessages.hpp \
|
||||
src/util/SharedPtrElementLess.hpp \
|
||||
src/util/StandardItemHelper.hpp \
|
||||
src/util/StreamLink.hpp \
|
||||
|
|
|
@ -44,12 +44,12 @@
|
|||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
BaseWindow::BaseWindow(QWidget *parent, Flags _flags)
|
||||
BaseWindow::BaseWindow(FlagsEnum<Flags> _flags, QWidget *parent)
|
||||
: BaseWidget(parent,
|
||||
Qt::Window | ((_flags & TopMost) ? Qt::WindowStaysOnTopHint
|
||||
Qt::Window | (_flags.has(TopMost) ? Qt::WindowStaysOnTopHint
|
||||
: Qt::WindowFlags()))
|
||||
, enableCustomFrame_(_flags & EnableCustomFrame)
|
||||
, frameless_(_flags & Frameless)
|
||||
, enableCustomFrame_(_flags.has(EnableCustomFrame))
|
||||
, frameless_(_flags.has(Frameless))
|
||||
, flags_(_flags)
|
||||
{
|
||||
if (this->frameless_)
|
||||
|
@ -74,9 +74,13 @@ BaseWindow::BaseWindow(QWidget *parent, Flags _flags)
|
|||
createWindowShortcut(this, "CTRL+0",
|
||||
[] { getSettings()->uiScale.setValue(1); });
|
||||
|
||||
// QTimer::this->scaleChangedEvent(this->getScale());
|
||||
|
||||
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)
|
||||
|
@ -107,11 +111,6 @@ float BaseWindow::qtFontScale() const
|
|||
return this->scale() / this->nativeScale_;
|
||||
}
|
||||
|
||||
BaseWindow::Flags BaseWindow::getFlags()
|
||||
{
|
||||
return this->flags_;
|
||||
}
|
||||
|
||||
void BaseWindow::init()
|
||||
{
|
||||
this->setWindowIcon(QIcon(":/images/icon.png"));
|
||||
|
@ -200,7 +199,7 @@ void BaseWindow::init()
|
|||
|
||||
#ifdef USEWINSDK
|
||||
// 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] {
|
||||
getSettings()->windowTopMost.connect(
|
||||
|
@ -364,7 +363,7 @@ void BaseWindow::onFocusLost()
|
|||
void BaseWindow::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
#ifndef Q_OS_WIN
|
||||
if (this->flags_ & FramelessDraggable)
|
||||
if (this->flags_.has(FramelessDraggable))
|
||||
{
|
||||
this->movingRelativePos = event->localPos();
|
||||
if (auto widget =
|
||||
|
@ -400,7 +399,7 @@ void BaseWindow::mousePressEvent(QMouseEvent *event)
|
|||
void BaseWindow::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
#ifndef Q_OS_WIN
|
||||
if (this->flags_ & FramelessDraggable)
|
||||
if (this->flags_.has(FramelessDraggable))
|
||||
{
|
||||
if (this->moving)
|
||||
{
|
||||
|
@ -416,7 +415,7 @@ void BaseWindow::mouseReleaseEvent(QMouseEvent *event)
|
|||
void BaseWindow::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
#ifndef Q_OS_WIN
|
||||
if (this->flags_ & FramelessDraggable)
|
||||
if (this->flags_.has(FramelessDraggable))
|
||||
{
|
||||
if (this->moving)
|
||||
{
|
||||
|
@ -515,7 +514,7 @@ void BaseWindow::resizeEvent(QResizeEvent *)
|
|||
getApp()->windows->queueSave();
|
||||
#endif
|
||||
|
||||
this->moveIntoDesktopRect(this);
|
||||
//this->moveIntoDesktopRect(this);
|
||||
|
||||
this->calcButtonsSizes();
|
||||
}
|
||||
|
@ -535,6 +534,18 @@ void BaseWindow::closeEvent(QCloseEvent *)
|
|||
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)
|
||||
{
|
||||
if (!this->stayInScreenRect_)
|
||||
|
@ -643,7 +654,7 @@ void BaseWindow::paintEvent(QPaintEvent *)
|
|||
void BaseWindow::updateScale()
|
||||
{
|
||||
auto scale =
|
||||
this->nativeScale_ * (this->flags_ & DisableCustomScaling
|
||||
this->nativeScale_ * (this->flags_.has(DisableCustomScaling)
|
||||
? 1
|
||||
: getABSettings()->getClampedUiScale());
|
||||
|
||||
|
@ -702,17 +713,11 @@ bool BaseWindow::handleDPICHANGED(MSG *msg)
|
|||
|
||||
float _scale = dpi / 96.f;
|
||||
|
||||
static bool firstResize = true;
|
||||
|
||||
if (!firstResize)
|
||||
{
|
||||
auto *prcNewWindow = reinterpret_cast<RECT *>(msg->lParam);
|
||||
SetWindowPos(msg->hwnd, nullptr, prcNewWindow->left, prcNewWindow->top,
|
||||
prcNewWindow->right - prcNewWindow->left,
|
||||
prcNewWindow->bottom - prcNewWindow->top,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
firstResize = false;
|
||||
|
||||
this->nativeScale_ = _scale;
|
||||
this->updateScale();
|
||||
|
@ -809,8 +814,10 @@ bool BaseWindow::handleSIZE(MSG *msg)
|
|||
{
|
||||
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;
|
||||
::GetWindowRect(msg->hwnd, &rect);
|
||||
|
@ -818,6 +825,7 @@ bool BaseWindow::handleSIZE(MSG *msg)
|
|||
QRect(QPoint(rect.left, rect.top),
|
||||
QPoint(rect.right - 1, rect.bottom - 1));
|
||||
}
|
||||
this->useNextBounds_.stop();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -833,8 +841,10 @@ bool BaseWindow::handleMOVE(MSG *msg)
|
|||
{
|
||||
RECT 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));
|
||||
|
||||
this->useNextBounds_.start(10);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
|
@ -942,7 +952,7 @@ bool BaseWindow::handleNCHITTEST(MSG *msg, long *result)
|
|||
|
||||
return true;
|
||||
}
|
||||
else if (this->flags_ & FramelessDraggable)
|
||||
else if (this->flags_.has(FramelessDraggable))
|
||||
{
|
||||
*result = 0;
|
||||
bool client = false;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <functional>
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
#include "common/FlagsEnum.hpp"
|
||||
|
||||
class QHBoxLayout;
|
||||
struct tagMSG;
|
||||
|
@ -32,7 +33,8 @@ public:
|
|||
|
||||
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);
|
||||
QRect getBounds();
|
||||
|
@ -54,8 +56,6 @@ public:
|
|||
virtual float scale() const override;
|
||||
float qtFontScale() const;
|
||||
|
||||
Flags getFlags();
|
||||
|
||||
pajlada::Signals::NoArgSignal closing;
|
||||
|
||||
static bool supportsCustomWindowFrame();
|
||||
|
@ -72,6 +72,7 @@ protected:
|
|||
virtual void resizeEvent(QResizeEvent *) override;
|
||||
virtual void moveEvent(QMoveEvent *) override;
|
||||
virtual void closeEvent(QCloseEvent *) override;
|
||||
virtual void showEvent(QShowEvent *) override;
|
||||
|
||||
virtual void themeChangedEvent() override;
|
||||
virtual bool event(QEvent *event) override;
|
||||
|
@ -106,7 +107,7 @@ private:
|
|||
bool frameless_;
|
||||
bool stayInScreenRect_ = false;
|
||||
bool shown_ = false;
|
||||
Flags flags_;
|
||||
FlagsEnum<Flags> flags_;
|
||||
float nativeScale_ = 1;
|
||||
|
||||
struct {
|
||||
|
@ -123,6 +124,8 @@ private:
|
|||
#ifdef USEWINSDK
|
||||
QRect initalBounds_;
|
||||
QRect currentBounds_;
|
||||
QRect nextBounds_;
|
||||
QTimer useNextBounds_;
|
||||
bool isNotMinimizedOrMaximized_{};
|
||||
#endif
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ TooltipWidget *TooltipWidget::getInstance()
|
|||
}
|
||||
|
||||
TooltipWidget::TooltipWidget(BaseWidget *parent)
|
||||
: BaseWindow(parent, BaseWindow::TopMost)
|
||||
: BaseWindow(BaseWindow::TopMost, parent)
|
||||
, displayImage_(new QLabel())
|
||||
, displayText_(new QLabel())
|
||||
{
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 19cad9925f83d15d7487c16f0491f4741ec9f674
|
||||
Subproject commit 1e0138c7ccedc6be859d28270ccd6195f235a94e
|
10
src/ForwardDecl.hpp
Normal file
10
src/ForwardDecl.hpp
Normal 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
|
|
@ -1,3 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
# include <fmt/format.h>
|
||||
# include <irccommand.h>
|
||||
|
|
36
src/common/Modes.cpp
Normal file
36
src/common/Modes.cpp
Normal 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
16
src/common/Modes.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Modes
|
||||
{
|
||||
public:
|
||||
Modes();
|
||||
|
||||
static const Modes &getInstance();
|
||||
|
||||
bool isNightly{};
|
||||
bool isPortable{};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -32,6 +32,10 @@ NetworkData::~NetworkData()
|
|||
|
||||
QString NetworkData::getHash()
|
||||
{
|
||||
static std::mutex mu;
|
||||
|
||||
std::lock_guard lock(mu);
|
||||
|
||||
if (this->hash_.isEmpty())
|
||||
{
|
||||
QByteArray bytes;
|
||||
|
@ -242,7 +246,6 @@ void load(const std::shared_ptr<NetworkData> &data)
|
|||
if (data->cache_)
|
||||
{
|
||||
QtConcurrent::run(loadCached, data);
|
||||
loadCached(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <QtGlobal>
|
||||
|
||||
#define CHATTERINO_VERSION "2.1.4-beta-1"
|
||||
#define CHATTERINO_VERSION "2.1.4"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
# define CHATTERINO_OS "win"
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace chatterino {
|
|||
|
||||
// commandmodel
|
||||
HighlightModel::HighlightModel(QObject *parent)
|
||||
: SignalVectorModel<HighlightPhrase>(4, parent)
|
||||
: SignalVectorModel<HighlightPhrase>(5, parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -16,12 +16,13 @@ HighlightModel::HighlightModel(QObject *parent)
|
|||
HighlightPhrase HighlightModel::getItemFromRow(
|
||||
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
||||
{
|
||||
// key, alert, sound, regex
|
||||
// key, alert, sound, regex, case-sensitivity
|
||||
|
||||
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
|
||||
row[1]->data(Qt::CheckStateRole).toBool(),
|
||||
row[2]->data(Qt::CheckStateRole).toBool(),
|
||||
row[3]->data(Qt::CheckStateRole).toBool()};
|
||||
row[3]->data(Qt::CheckStateRole).toBool(),
|
||||
row[4]->data(Qt::CheckStateRole).toBool()};
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
|
@ -32,6 +33,7 @@ void HighlightModel::getRowFromItem(const HighlightPhrase &item,
|
|||
setBoolItem(row[1], item.getAlert());
|
||||
setBoolItem(row[2], item.getSound());
|
||||
setBoolItem(row[3], item.isRegex());
|
||||
setBoolItem(row[4], item.isCaseSensitive());
|
||||
}
|
||||
|
||||
void HighlightModel::afterInit()
|
||||
|
|
|
@ -15,21 +15,24 @@ public:
|
|||
bool operator==(const HighlightPhrase &other) const
|
||||
{
|
||||
return std::tie(this->pattern_, this->sound_, this->alert_,
|
||||
this->isRegex_) == std::tie(other.pattern_,
|
||||
other.sound_, other.alert_,
|
||||
other.isRegex_);
|
||||
this->isRegex_, this->caseSensitive_) ==
|
||||
std::tie(other.pattern_, other.sound_, other.alert_,
|
||||
other.isRegex_, other.caseSensitive_);
|
||||
}
|
||||
|
||||
HighlightPhrase(const QString &pattern, bool alert, bool sound,
|
||||
bool isRegex)
|
||||
bool isRegex, bool caseSensitive)
|
||||
: pattern_(pattern)
|
||||
, alert_(alert)
|
||||
, sound_(sound)
|
||||
, isRegex_(isRegex)
|
||||
, regex_(isRegex_ ? pattern
|
||||
, caseSensitive_(caseSensitive)
|
||||
, regex_(
|
||||
isRegex_ ? pattern
|
||||
: "\\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();
|
||||
}
|
||||
|
||||
bool isCaseSensitive() const
|
||||
{
|
||||
return this->caseSensitive_;
|
||||
}
|
||||
|
||||
private:
|
||||
QString pattern_;
|
||||
bool alert_;
|
||||
bool sound_;
|
||||
bool isRegex_;
|
||||
bool caseSensitive_;
|
||||
QRegularExpression regex_;
|
||||
};
|
||||
} // namespace chatterino
|
||||
|
@ -82,6 +91,7 @@ struct Serialize<chatterino::HighlightPhrase> {
|
|||
chatterino::rj::set(ret, "alert", value.getAlert(), a);
|
||||
chatterino::rj::set(ret, "sound", value.getSound(), a);
|
||||
chatterino::rj::set(ret, "regex", value.isRegex(), a);
|
||||
chatterino::rj::set(ret, "case", value.isCaseSensitive(), a);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -93,20 +103,24 @@ struct Deserialize<chatterino::HighlightPhrase> {
|
|||
{
|
||||
if (!value.IsObject())
|
||||
{
|
||||
return chatterino::HighlightPhrase(QString(), true, false, false);
|
||||
return chatterino::HighlightPhrase(QString(), true, false, false,
|
||||
false);
|
||||
}
|
||||
|
||||
QString _pattern;
|
||||
bool _alert = true;
|
||||
bool _sound = false;
|
||||
bool _isRegex = false;
|
||||
bool _caseSensitive = false;
|
||||
|
||||
chatterino::rj::getSafe(value, "pattern", _pattern);
|
||||
chatterino::rj::getSafe(value, "alert", _alert);
|
||||
chatterino::rj::getSafe(value, "sound", _sound);
|
||||
chatterino::rj::getSafe(value, "regex", _isRegex);
|
||||
chatterino::rj::getSafe(value, "case", _caseSensitive);
|
||||
|
||||
return chatterino::HighlightPhrase(_pattern, _alert, _sound, _isRegex);
|
||||
return chatterino::HighlightPhrase(_pattern, _alert, _sound,
|
||||
_isRegex, _caseSensitive);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace chatterino {
|
|||
|
||||
// commandmodel
|
||||
UserHighlightModel::UserHighlightModel(QObject *parent)
|
||||
: SignalVectorModel<HighlightPhrase>(4, parent)
|
||||
: SignalVectorModel<HighlightPhrase>(5, parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,8 @@ HighlightPhrase UserHighlightModel::getItemFromRow(
|
|||
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
|
||||
row[1]->data(Qt::CheckStateRole).toBool(),
|
||||
row[2]->data(Qt::CheckStateRole).toBool(),
|
||||
row[3]->data(Qt::CheckStateRole).toBool()};
|
||||
row[3]->data(Qt::CheckStateRole).toBool(),
|
||||
row[4]->data(Qt::CheckStateRole).toBool()};
|
||||
}
|
||||
|
||||
// row into vector item
|
||||
|
@ -32,6 +33,7 @@ void UserHighlightModel::getRowFromItem(const HighlightPhrase &item,
|
|||
setBoolItem(row[1], item.getAlert());
|
||||
setBoolItem(row[2], item.getSound());
|
||||
setBoolItem(row[3], item.isRegex());
|
||||
setBoolItem(row[4], item.isCaseSensitive());
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -22,7 +22,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
std::size_t size()
|
||||
std::size_t size() const
|
||||
{
|
||||
return this->length_;
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
|
|||
QSize(int(container.getScale() * image->width() * emoteScale),
|
||||
int(container.getScale() * image->height() * emoteScale));
|
||||
|
||||
container.addElement((new ImageLayoutElement(*this, image, size))
|
||||
container.addElement(this->makeImageLayoutElement(image, size)
|
||||
->setLink(this->getLink()));
|
||||
}
|
||||
else
|
||||
|
@ -159,6 +159,27 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
|
|||
}
|
||||
}
|
||||
|
||||
MessageLayoutElement *EmoteElement::makeImageLayoutElement(
|
||||
const ImagePtr &image, const QSize &size)
|
||||
{
|
||||
return new ImageLayoutElement(*this, image, size);
|
||||
}
|
||||
|
||||
// MOD BADGE
|
||||
ModBadgeElement::ModBadgeElement(const EmotePtr &data,
|
||||
MessageElementFlags flags_)
|
||||
: EmoteElement(data, flags_)
|
||||
{
|
||||
}
|
||||
|
||||
MessageLayoutElement *ModBadgeElement::makeImageLayoutElement(
|
||||
const ImagePtr &image, const QSize &size)
|
||||
{
|
||||
static const QColor modBadgeBackgroundColor("#34AE0A");
|
||||
return new ImageWithBackgroundLayoutElement(*this, image, size,
|
||||
modBadgeBackgroundColor);
|
||||
}
|
||||
|
||||
// BADGE
|
||||
BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags)
|
||||
: MessageElement(flags)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
namespace chatterino {
|
||||
class Channel;
|
||||
struct MessageLayoutContainer;
|
||||
class MessageLayoutElement;
|
||||
|
||||
class Image;
|
||||
using ImagePtr = std::shared_ptr<Image>;
|
||||
|
@ -209,11 +210,26 @@ public:
|
|||
MessageElementFlags flags_) override;
|
||||
EmotePtr getEmote() const;
|
||||
|
||||
protected:
|
||||
virtual MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
|
||||
const QSize &size);
|
||||
|
||||
private:
|
||||
std::unique_ptr<TextElement> textElement_;
|
||||
EmotePtr emote_;
|
||||
};
|
||||
|
||||
// Behaves like an emote element, except it creates a different image layout element that draws the mod badge background
|
||||
class ModBadgeElement : public EmoteElement
|
||||
{
|
||||
public:
|
||||
ModBadgeElement(const EmotePtr &data, MessageElementFlags flags_);
|
||||
|
||||
protected:
|
||||
MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
|
||||
const QSize &size) override;
|
||||
};
|
||||
|
||||
class BadgeElement : public MessageElement
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -168,6 +168,33 @@ int ImageLayoutElement::getXFromIndex(int index)
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// IMAGE WITH BACKGROUND
|
||||
//
|
||||
ImageWithBackgroundLayoutElement::ImageWithBackgroundLayoutElement(
|
||||
MessageElement &creator, ImagePtr image, const QSize &size, QColor color)
|
||||
: ImageLayoutElement(creator, image, size)
|
||||
, color_(color)
|
||||
{
|
||||
}
|
||||
|
||||
void ImageWithBackgroundLayoutElement::paint(QPainter &painter)
|
||||
{
|
||||
if (this->image_ == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto pixmap = this->image_->pixmapOrLoad();
|
||||
if (pixmap && !this->image_->animated())
|
||||
{
|
||||
painter.fillRect(QRectF(this->getRect()), this->color_);
|
||||
|
||||
// fourtf: make it use qreal values
|
||||
painter.drawPixmap(QRectF(this->getRect()), *pixmap, QRectF());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// TEXT
|
||||
//
|
||||
|
|
|
@ -72,10 +72,22 @@ protected:
|
|||
int getMouseOverIndex(const QPoint &abs) const override;
|
||||
int getXFromIndex(int index) override;
|
||||
|
||||
private:
|
||||
ImagePtr image_;
|
||||
};
|
||||
|
||||
class ImageWithBackgroundLayoutElement : public ImageLayoutElement
|
||||
{
|
||||
public:
|
||||
ImageWithBackgroundLayoutElement(MessageElement &creator, ImagePtr image,
|
||||
const QSize &size, QColor color);
|
||||
|
||||
protected:
|
||||
void paint(QPainter &painter) override;
|
||||
|
||||
private:
|
||||
QColor color_;
|
||||
};
|
||||
|
||||
// TEXT
|
||||
class TextLayoutElement : public MessageLayoutElement
|
||||
{
|
||||
|
|
24
src/messages/search/AuthorPredicate.cpp
Normal file
24
src/messages/search/AuthorPredicate.cpp
Normal 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
|
38
src/messages/search/AuthorPredicate.hpp
Normal file
38
src/messages/search/AuthorPredicate.hpp
Normal 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
|
22
src/messages/search/LinkPredicate.cpp
Normal file
22
src/messages/search/LinkPredicate.cpp
Normal 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
|
26
src/messages/search/LinkPredicate.hpp
Normal file
26
src/messages/search/LinkPredicate.hpp
Normal 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
|
31
src/messages/search/MessagePredicate.hpp
Normal file
31
src/messages/search/MessagePredicate.hpp
Normal 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
|
15
src/messages/search/SubstringPredicate.cpp
Normal file
15
src/messages/search/SubstringPredicate.cpp
Normal 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
|
41
src/messages/search/SubstringPredicate.hpp
Normal file
41
src/messages/search/SubstringPredicate.hpp
Normal 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
|
|
@ -79,7 +79,38 @@ namespace {
|
|||
|
||||
return {Success, std::move(emotes)};
|
||||
}
|
||||
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
|
||||
|
||||
boost::optional<EmotePtr> parseModBadge(const QJsonObject &jsonRoot)
|
||||
{
|
||||
boost::optional<EmotePtr> modBadge;
|
||||
|
||||
auto room = jsonRoot.value("room").toObject();
|
||||
auto modUrls = room.value("mod_urls").toObject();
|
||||
if (!modUrls.isEmpty())
|
||||
{
|
||||
auto modBadge1x = getEmoteLink(modUrls, "1");
|
||||
auto modBadge2x = getEmoteLink(modUrls, "2");
|
||||
auto modBadge3x = getEmoteLink(modUrls, "4");
|
||||
|
||||
auto modBadgeImageSet = ImageSet{
|
||||
Image::fromUrl(modBadge1x, 1),
|
||||
modBadge2x.string.isEmpty() ? Image::getEmpty()
|
||||
: Image::fromUrl(modBadge2x, 0.5),
|
||||
modBadge3x.string.isEmpty() ? Image::getEmpty()
|
||||
: Image::fromUrl(modBadge3x, 0.25),
|
||||
};
|
||||
|
||||
modBadge = std::make_shared<Emote>(Emote{
|
||||
{""},
|
||||
modBadgeImageSet,
|
||||
Tooltip{"Twitch Channel Moderator"},
|
||||
modBadge1x,
|
||||
});
|
||||
}
|
||||
return modBadge;
|
||||
}
|
||||
|
||||
EmoteMap parseChannelEmotes(const QJsonObject &jsonRoot)
|
||||
{
|
||||
auto jsonSets = jsonRoot.value("sets").toObject();
|
||||
auto emotes = EmoteMap();
|
||||
|
@ -110,7 +141,7 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
return {Success, std::move(emotes)};
|
||||
return emotes;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -151,19 +182,26 @@ void FfzEmotes::loadEmotes()
|
|||
.execute();
|
||||
}
|
||||
|
||||
void FfzEmotes::loadChannel(const QString &channelId,
|
||||
std::function<void(EmoteMap &&)> callback)
|
||||
void FfzEmotes::loadChannel(
|
||||
const QString &channelId, std::function<void(EmoteMap &&)> emoteCallback,
|
||||
std::function<void(boost::optional<EmotePtr>)> modBadgeCallback)
|
||||
{
|
||||
log("[FFZEmotes] Reload FFZ Channel Emotes for channel {}\n", channelId);
|
||||
|
||||
NetworkRequest("https://api.frankerfacez.com/v1/room/id/" + channelId)
|
||||
|
||||
.timeout(20000)
|
||||
.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
|
||||
auto pair = parseChannelEmotes(result.parseJson());
|
||||
if (pair.first)
|
||||
callback(std::move(pair.second));
|
||||
return pair.first;
|
||||
.onSuccess([emoteCallback = std::move(emoteCallback),
|
||||
modBadgeCallback =
|
||||
std::move(modBadgeCallback)](auto result) -> Outcome {
|
||||
auto json = result.parseJson();
|
||||
auto emoteMap = parseChannelEmotes(json);
|
||||
auto modBadge = parseModBadge(json);
|
||||
|
||||
emoteCallback(std::move(emoteMap));
|
||||
modBadgeCallback(std::move(modBadge));
|
||||
|
||||
return Success;
|
||||
})
|
||||
.onError([channelId](int result) {
|
||||
if (result == 203)
|
||||
|
|
|
@ -22,8 +22,10 @@ public:
|
|||
std::shared_ptr<const EmoteMap> emotes() const;
|
||||
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
||||
void loadEmotes();
|
||||
static void loadChannel(const QString &channelId,
|
||||
std::function<void(EmoteMap &&)> callback);
|
||||
static void loadChannel(
|
||||
const QString &channelId,
|
||||
std::function<void(EmoteMap &&)> emoteCallback,
|
||||
std::function<void(boost::optional<EmotePtr>)> modBadgeCallback);
|
||||
|
||||
private:
|
||||
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
#include "FfzModBadge.hpp"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QImageReader>
|
||||
#include <QJsonObject>
|
||||
#include <QPainter>
|
||||
#include <QString>
|
||||
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
FfzModBadge::FfzModBadge(const QString &channelName)
|
||||
: channelName_(channelName)
|
||||
{
|
||||
}
|
||||
|
||||
void FfzModBadge::loadCustomModBadge()
|
||||
{
|
||||
static QString partialUrl("https://cdn.frankerfacez.com/room-badge/mod/");
|
||||
|
||||
QString url = partialUrl + channelName_ + "/1";
|
||||
NetworkRequest(url)
|
||||
|
||||
.onSuccess([this, url](auto result) -> Outcome {
|
||||
auto data = result.getData();
|
||||
|
||||
QBuffer buffer(const_cast<QByteArray *>(&data));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
QImageReader reader(&buffer);
|
||||
if (reader.imageCount() == 0)
|
||||
return Failure;
|
||||
|
||||
QPixmap badgeOverlay = QPixmap::fromImageReader(&reader);
|
||||
QPixmap badgePixmap(18, 18);
|
||||
|
||||
// the default mod badge green color
|
||||
badgePixmap.fill(QColor("#34AE0A"));
|
||||
QPainter painter(&badgePixmap);
|
||||
QRectF rect(0, 0, 18, 18);
|
||||
painter.drawPixmap(rect, badgeOverlay, rect);
|
||||
|
||||
auto emote = Emote{{""},
|
||||
ImageSet{Image::fromPixmap(badgePixmap)},
|
||||
Tooltip{"Twitch Channel Moderator"},
|
||||
Url{url}};
|
||||
|
||||
this->badge_ = std::make_shared<Emote>(emote);
|
||||
// getBadge.execute();
|
||||
|
||||
return Success;
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
EmotePtr FfzModBadge::badge() const
|
||||
{
|
||||
return this->badge_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
|
@ -1,25 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct Emote;
|
||||
using EmotePtr = std::shared_ptr<const Emote>;
|
||||
|
||||
class FfzModBadge
|
||||
{
|
||||
public:
|
||||
FfzModBadge(const QString &channelName);
|
||||
|
||||
void loadCustomModBadge();
|
||||
|
||||
EmotePtr badge() const;
|
||||
|
||||
private:
|
||||
const QString channelName_;
|
||||
EmotePtr badge_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -48,8 +48,14 @@ void ChatroomChannel::refreshFFZChannelEmotes()
|
|||
{
|
||||
return;
|
||||
}
|
||||
FfzEmotes::loadChannel(this->chatroomOwnerId, [this](auto &&emoteMap) {
|
||||
this->ffzEmotes_.set(std::make_shared<EmoteMap>(std::move(emoteMap)));
|
||||
FfzEmotes::loadChannel(
|
||||
this->chatroomOwnerId,
|
||||
[this](auto &&emoteMap) {
|
||||
this->ffzEmotes_.set(
|
||||
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
||||
},
|
||||
[this](auto &&modBadge) {
|
||||
this->ffzCustomModBadge_.set(std::move(modBadge));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -51,8 +51,8 @@ public:
|
|||
|
||||
private:
|
||||
void addMessage(Communi::IrcMessage *message, const QString &target,
|
||||
const QString &content, TwitchIrcServer &server, bool isResub,
|
||||
bool isAction);
|
||||
const QString &content, TwitchIrcServer &server,
|
||||
bool isResub, bool isAction);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -88,7 +88,6 @@ TwitchChannel::TwitchChannel(const QString &name,
|
|||
, globalFfz_(ffz)
|
||||
, bttvEmotes_(std::make_shared<EmoteMap>())
|
||||
, ffzEmotes_(std::make_shared<EmoteMap>())
|
||||
, ffzCustomModBadge_(name)
|
||||
, mod_(false)
|
||||
{
|
||||
log("[TwitchChannel:{}] Opened", name);
|
||||
|
@ -139,7 +138,6 @@ void TwitchChannel::initialize()
|
|||
{
|
||||
this->refreshChatters();
|
||||
this->refreshBadges();
|
||||
this->ffzCustomModBadge_.loadCustomModBadge();
|
||||
}
|
||||
|
||||
bool TwitchChannel::isEmpty() const
|
||||
|
@ -165,10 +163,17 @@ void TwitchChannel::refreshBTTVChannelEmotes()
|
|||
void TwitchChannel::refreshFFZChannelEmotes()
|
||||
{
|
||||
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())
|
||||
this->ffzEmotes_.set(
|
||||
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
||||
},
|
||||
[this, weak = weakOf<Channel>(this)](auto &&modBadge) {
|
||||
if (auto shared = weak.lock())
|
||||
{
|
||||
this->ffzCustomModBadge_.set(std::move(modBadge));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -685,22 +690,23 @@ void TwitchChannel::refreshBadges()
|
|||
|
||||
void TwitchChannel::refreshCheerEmotes()
|
||||
{
|
||||
/*auto url = Url{"https://api.twitch.tv/kraken/bits/actions?channel_id=" +
|
||||
this->getRoomId()};
|
||||
auto request = NetworkRequest::twitchRequest(url.string);
|
||||
request.setCaller(QThread::currentThread());
|
||||
|
||||
request.onSuccess(
|
||||
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||
QString url("https://api.twitch.tv/kraken/bits/actions?channel_id=" +
|
||||
this->roomId());
|
||||
NetworkRequest::twitchRequest(url)
|
||||
.onSuccess([this,
|
||||
weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
|
||||
std::vector<CheerEmoteSet> emoteSets;
|
||||
|
||||
for (auto &set : cheerEmoteSets) {
|
||||
for (auto &set : cheerEmoteSets)
|
||||
{
|
||||
auto cheerEmoteSet = CheerEmoteSet();
|
||||
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.color = QColor(tier.color);
|
||||
|
@ -733,7 +739,7 @@ void TwitchChannel::refreshCheerEmotes()
|
|||
std::sort(cheerEmoteSet.cheerEmotes.begin(),
|
||||
cheerEmoteSet.cheerEmotes.end(),
|
||||
[](const auto &lhs, const auto &rhs) {
|
||||
return lhs.minBits < rhs.minBits; //
|
||||
return lhs.minBits > rhs.minBits;
|
||||
});
|
||||
|
||||
emoteSets.emplace_back(cheerEmoteSet);
|
||||
|
@ -741,10 +747,8 @@ void TwitchChannel::refreshCheerEmotes()
|
|||
*this->cheerEmoteSets_.access() = std::move(emoteSets);
|
||||
|
||||
return Success;
|
||||
});
|
||||
|
||||
request.execute();
|
||||
*/
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
boost::optional<EmotePtr> TwitchChannel::twitchBadge(
|
||||
|
@ -765,9 +769,34 @@ boost::optional<EmotePtr> TwitchChannel::twitchBadge(
|
|||
|
||||
boost::optional<EmotePtr> TwitchChannel::ffzCustomModBadge() const
|
||||
{
|
||||
if (auto badge = this->ffzCustomModBadge_.badge())
|
||||
return badge;
|
||||
return this->ffzCustomModBadge_.get();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "common/ChannelChatters.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/UniqueAccess.hpp"
|
||||
#include "providers/ffz/FfzModBadge.hpp"
|
||||
#include "common/UsernameSet.hpp"
|
||||
#include "providers/twitch/TwitchEmotes.hpp"
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
|
@ -96,6 +96,9 @@ public:
|
|||
boost::optional<EmotePtr> twitchBadge(const QString &set,
|
||||
const QString &version) const;
|
||||
|
||||
// Cheers
|
||||
boost::optional<CheerEmote> cheerEmote(const QString &string);
|
||||
|
||||
// Signals
|
||||
pajlada::Signals::NoArgSignal roomIdChanged;
|
||||
pajlada::Signals::NoArgSignal userStateChanged;
|
||||
|
@ -145,13 +148,13 @@ protected:
|
|||
FfzEmotes &globalFfz_;
|
||||
Atomic<std::shared_ptr<const EmoteMap>> bttvEmotes_;
|
||||
Atomic<std::shared_ptr<const EmoteMap>> ffzEmotes_;
|
||||
Atomic<boost::optional<EmotePtr>> ffzCustomModBadge_;
|
||||
|
||||
private:
|
||||
// Badges
|
||||
UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>>
|
||||
badgeSets_; // "subscribers": { "0": ... "3": ... "6": ...
|
||||
UniqueAccess<std::vector<CheerEmoteSet>> cheerEmoteSets_;
|
||||
FfzModBadge ffzCustomModBadge_;
|
||||
|
||||
bool mod_ = false;
|
||||
bool vip_ = false;
|
||||
|
|
|
@ -143,9 +143,21 @@ bool TwitchMessageBuilder::isIgnored() const
|
|||
return false;
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::triggerHighlights()
|
||||
inline QMediaPlayer *getPlayer()
|
||||
{
|
||||
if (isGuiThread())
|
||||
{
|
||||
static auto player = new QMediaPlayer;
|
||||
return player;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::triggerHighlights()
|
||||
{
|
||||
static QUrl currentPlayerUrl;
|
||||
|
||||
if (this->historicalMessage_)
|
||||
|
@ -164,6 +176,8 @@ void TwitchMessageBuilder::triggerHighlights()
|
|||
bool resolveFocus = !hasFocus || getSettings()->highlightAlwaysPlaySound;
|
||||
|
||||
if (this->highlightSound_ && resolveFocus)
|
||||
{
|
||||
if (auto player = getPlayer())
|
||||
{
|
||||
// update the media player url if necessary
|
||||
QUrl highlightSoundUrl =
|
||||
|
@ -181,6 +195,7 @@ void TwitchMessageBuilder::triggerHighlights()
|
|||
|
||||
player->play();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->highlightAlert_)
|
||||
{
|
||||
|
@ -274,7 +289,7 @@ MessagePtr TwitchMessageBuilder::build()
|
|||
if (iterator != this->tags.end())
|
||||
{
|
||||
this->hasBits_ = true;
|
||||
// bits = iterator.value().toString();
|
||||
this->bits = iterator.value().toString();
|
||||
}
|
||||
|
||||
// twitch emotes
|
||||
|
@ -340,7 +355,7 @@ void TwitchMessageBuilder::addWords(
|
|||
auto i = int();
|
||||
auto currentTwitchEmote = twitchEmotes.begin();
|
||||
|
||||
for (const auto &word : words)
|
||||
for (auto word : words)
|
||||
{
|
||||
// check if it's a twitch emote twitch emote
|
||||
while (currentTwitchEmote != twitchEmotes.end() &&
|
||||
|
@ -361,10 +376,20 @@ void TwitchMessageBuilder::addWords(
|
|||
MessageElementFlag::TwitchEmote);
|
||||
|
||||
i += word.length() + 1;
|
||||
|
||||
int len = std::get<2>(*currentTwitchEmote).string.length();
|
||||
currentTwitchEmote++;
|
||||
|
||||
if (len < word.length())
|
||||
{
|
||||
word = word.mid(len);
|
||||
this->message().elements.back()->setTrailingSpace(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// split words
|
||||
for (auto &variant : getApp()->emotes->emojis.parse(word))
|
||||
|
@ -952,7 +977,7 @@ void TwitchMessageBuilder::parseHighlights()
|
|||
{
|
||||
HighlightPhrase selfHighlight(
|
||||
currentUsername, getSettings()->enableSelfHighlightTaskbar,
|
||||
getSettings()->enableSelfHighlightSound, false);
|
||||
getSettings()->enableSelfHighlightSound, false, false);
|
||||
activeHighlights.emplace_back(std::move(selfHighlight));
|
||||
}
|
||||
|
||||
|
@ -1171,7 +1196,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
|||
{
|
||||
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
|
||||
{
|
||||
this->emplace<BadgeElement>(
|
||||
this->emplace<ModBadgeElement>(
|
||||
customModBadge.get(),
|
||||
MessageElementFlag::BadgeChannelAuthority)
|
||||
->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;
|
||||
}
|
||||
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
|
||||
|
|
|
@ -78,6 +78,7 @@ private:
|
|||
|
||||
QString roomID_;
|
||||
bool hasBits_ = false;
|
||||
QString bits;
|
||||
bool historicalMessage_ = false;
|
||||
|
||||
QString userId_;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <QStandardPaths>
|
||||
#include <cassert>
|
||||
|
||||
#include "common/Modes.hpp"
|
||||
#include "util/CombinePath.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
@ -32,7 +33,7 @@ bool Paths::createFolder(const QString &folderPath)
|
|||
|
||||
bool Paths::isPortable()
|
||||
{
|
||||
return this->portable_.get();
|
||||
return Modes::getInstance().isPortable;
|
||||
}
|
||||
|
||||
QString Paths::cacheDirectory()
|
||||
|
|
|
@ -208,6 +208,7 @@ public:
|
|||
BoolSetting loadTwitchMessageHistoryOnConnect = {
|
||||
"/misc/twitch/loadMessageHistoryOnConnect", true};
|
||||
IntSetting emotesTooltipPreview = {"/misc/emotesTooltipPreview", 0};
|
||||
BoolSetting openLinksIncognito = {"/misc/openLinksIncognito", 0};
|
||||
|
||||
QStringSetting cachePath = {"/cache/path", ""};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "Updates.hpp"
|
||||
|
||||
#include "Settings.hpp"
|
||||
#include "common/Modes.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/Version.hpp"
|
||||
|
@ -201,6 +202,14 @@ void Updates::installUpdates()
|
|||
|
||||
void Updates::checkForUpdates()
|
||||
{
|
||||
// Disable updates if on nightly and windows.
|
||||
#ifdef Q_OS_WIN
|
||||
if (Modes::getInstance().isNightly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
QString url =
|
||||
"https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/" +
|
||||
currentBranch();
|
||||
|
|
|
@ -82,8 +82,7 @@ void WindowManager::showAccountSelectPopup(QPoint point)
|
|||
w->refresh();
|
||||
|
||||
QPoint buttonPos = point;
|
||||
w->move(buttonPos.x(), buttonPos.y());
|
||||
|
||||
w->move(buttonPos.x() - 30, buttonPos.y());
|
||||
w->show();
|
||||
w->setFocus();
|
||||
}
|
||||
|
|
64
src/util/SampleCheerMessages.hpp
Normal file
64
src/util/SampleCheerMessages.hpp
Normal 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! Can’t 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
|
|
@ -11,9 +11,8 @@
|
|||
namespace chatterino {
|
||||
|
||||
AccountSwitchPopup::AccountSwitchPopup(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
: BaseWindow({BaseWindow::TopMost, BaseWindow::Frameless}, parent)
|
||||
{
|
||||
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
|
||||
#ifdef Q_OS_LINUX
|
||||
this->setWindowFlag(Qt::Popup);
|
||||
#endif
|
||||
|
@ -25,8 +24,6 @@ AccountSwitchPopup::AccountSwitchPopup(QWidget *parent)
|
|||
this->ui_.accountSwitchWidget->setFocusPolicy(Qt::NoFocus);
|
||||
vbox->addWidget(this->ui_.accountSwitchWidget);
|
||||
|
||||
// vbox->setSizeConstraint(QLayout::SetMinimumSize);
|
||||
|
||||
auto hbox = new QHBoxLayout();
|
||||
auto manageAccountsButton = new QPushButton(this);
|
||||
manageAccountsButton->setText("Manage Accounts");
|
||||
|
@ -38,9 +35,9 @@ AccountSwitchPopup::AccountSwitchPopup(QWidget *parent)
|
|||
SettingsDialog::showDialog(SettingsDialogPreference::Accounts); //
|
||||
});
|
||||
|
||||
this->setLayout(vbox);
|
||||
this->getLayoutContainer()->setLayout(vbox);
|
||||
|
||||
// this->setStyleSheet("background: #333");
|
||||
this->setScaleIndependantSize(200, 200);
|
||||
}
|
||||
|
||||
void AccountSwitchPopup::refresh()
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "widgets/AccountSwitchWidget.hpp"
|
||||
#include "widgets/BaseWindow.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class AccountSwitchPopup : public QWidget
|
||||
class AccountSwitchPopup : public BaseWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Application.hpp"
|
||||
#include "common/Credentials.hpp"
|
||||
#include "common/Modes.hpp"
|
||||
#include "common/Version.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
|
@ -23,6 +24,10 @@
|
|||
#include "widgets/splits/Split.hpp"
|
||||
#include "widgets/splits/SplitContainer.hpp"
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
# include "util/SampleCheerMessages.hpp"
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDesktopServices>
|
||||
#include <QHeaderView>
|
||||
|
@ -36,7 +41,7 @@
|
|||
namespace chatterino {
|
||||
|
||||
Window::Window(WindowType type)
|
||||
: BaseWindow(nullptr, BaseWindow::EnableCustomFrame)
|
||||
: BaseWindow(BaseWindow::EnableCustomFrame)
|
||||
, type_(type)
|
||||
, notebook_(new SplitNotebook(this))
|
||||
{
|
||||
|
@ -112,18 +117,10 @@ bool Window::event(QEvent *event)
|
|||
void Window::showEvent(QShowEvent *event)
|
||||
{
|
||||
// Startup notification
|
||||
if (getSettings()->startUpNotification.getValue() < 1)
|
||||
/*if (getSettings()->startUpNotification.getValue() < 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
|
||||
if (getSettings()->currentVersion.getValue() != "" &&
|
||||
|
@ -205,16 +202,9 @@ void Window::addDebugStuff()
|
|||
{
|
||||
#ifdef QT_DEBUG
|
||||
std::vector<QString> cheerMessages, subMessages, miscMessages;
|
||||
|
||||
cheerMessages = getSampleCheerMessage();
|
||||
// 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,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);
|
||||
});
|
||||
|
||||
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", [=] {
|
||||
auto *dialog = new WelcomeDialog();
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dialog->show();
|
||||
});
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -373,16 +371,25 @@ void Window::onAccountSelected()
|
|||
{
|
||||
auto user = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
//#ifdef CHATTERINO_NIGHTLY_VERSION_STRING
|
||||
// auto windowTitleEnd =
|
||||
// QString("Chatterino Nightly " CHATTERINO_VERSION
|
||||
// " (" UGLYMACROHACK(CHATTERINO_NIGHTLY_VERSION_STRING) ")");
|
||||
//#else
|
||||
auto windowTitleEnd = QString("Chatterino " CHATTERINO_VERSION);
|
||||
//#endif
|
||||
// update title
|
||||
QString title = "Chatterino ";
|
||||
if (Modes::getInstance().isNightly)
|
||||
{
|
||||
title += "Nightly ";
|
||||
}
|
||||
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 (this->userLabel_)
|
||||
|
@ -397,6 +404,6 @@ void Window::onAccountSelected()
|
|||
this->userLabel_->getLabel().setText(user->getUserName());
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace chatterino
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -101,7 +101,7 @@ namespace {
|
|||
} // namespace
|
||||
|
||||
EmotePopup::EmotePopup(QWidget *parent)
|
||||
: BaseWindow(parent, BaseWindow::EnableCustomFrame)
|
||||
: BaseWindow(BaseWindow::EnableCustomFrame, parent)
|
||||
{
|
||||
auto layout = new QVBoxLayout(this);
|
||||
this->getLayoutContainer()->setLayout(layout);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "common/Channel.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
|
@ -20,34 +21,30 @@ namespace chatterino {
|
|||
LogsPopup::LogsPopup()
|
||||
: channel_(Channel::getEmpty())
|
||||
{
|
||||
this->initLayout();
|
||||
this->resize(400, 600);
|
||||
}
|
||||
|
||||
void LogsPopup::initLayout()
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
|
||||
this->channelView_ = new ChannelView(this);
|
||||
layout->addWidget(this->channelView_);
|
||||
|
||||
this->setLayout(layout);
|
||||
}
|
||||
|
||||
void LogsPopup::setChannelName(QString channelName)
|
||||
{
|
||||
this->channelName_ = channelName;
|
||||
}
|
||||
|
||||
void LogsPopup::setChannel(std::shared_ptr<Channel> channel)
|
||||
void LogsPopup::setChannel(const ChannelPtr &channel)
|
||||
{
|
||||
this->channel_ = channel;
|
||||
this->updateWindowTitle();
|
||||
}
|
||||
|
||||
void LogsPopup::setTargetUserName(QString userName)
|
||||
void LogsPopup::setChannelName(const QString &channelName)
|
||||
{
|
||||
this->channelName_ = channelName;
|
||||
this->updateWindowTitle();
|
||||
}
|
||||
|
||||
void LogsPopup::setTargetUserName(const QString &userName)
|
||||
{
|
||||
this->userName_ = userName;
|
||||
this->updateWindowTitle();
|
||||
}
|
||||
|
||||
void LogsPopup::updateWindowTitle()
|
||||
{
|
||||
this->setWindowTitle(this->userName_ + "'s logs in #" + this->channelName_);
|
||||
}
|
||||
|
||||
void LogsPopup::getLogs()
|
||||
|
@ -60,8 +57,6 @@ void LogsPopup::getLogs()
|
|||
this->channelName_ = twitchChannel->getName();
|
||||
this->getLogviewerLogs(twitchChannel->roomId());
|
||||
|
||||
this->setWindowTitle(this->userName_ + "'s logs in #" +
|
||||
this->channelName_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +78,7 @@ void LogsPopup::setMessages(std::vector<MessagePtr> &messages)
|
|||
ChannelPtr logsChannel(new Channel("logs", Channel::Type::Misc));
|
||||
|
||||
logsChannel->addMessagesAtStart(messages);
|
||||
this->channelView_->setChannel(logsChannel);
|
||||
SearchPopup::setChannel(logsChannel);
|
||||
}
|
||||
|
||||
void LogsPopup::getLogviewerLogs(const QString &roomID)
|
||||
|
@ -121,6 +116,8 @@ void LogsPopup::getLogviewerLogs(const QString &roomID)
|
|||
static_cast<Communi::IrcPrivateMessage *>(ircMessage);
|
||||
TwitchMessageBuilder builder(this->channel_.get(), privMsg,
|
||||
args);
|
||||
builder.message().searchText = message;
|
||||
|
||||
messages.push_back(builder.build());
|
||||
}
|
||||
|
||||
|
@ -165,6 +162,7 @@ void LogsPopup::getOverrustleLogs()
|
|||
for (auto i : dataMessages)
|
||||
{
|
||||
QJsonObject singleMessage = i.toObject();
|
||||
auto text = singleMessage.value("text").toString();
|
||||
QTime timeStamp =
|
||||
QDateTime::fromSecsSinceEpoch(
|
||||
singleMessage.value("timestamp").toInt())
|
||||
|
@ -175,9 +173,10 @@ void LogsPopup::getOverrustleLogs()
|
|||
builder.emplace<TextElement>(this->userName_,
|
||||
MessageElementFlag::Username,
|
||||
MessageColor::System);
|
||||
builder.emplace<TextElement>(
|
||||
singleMessage.value("text").toString(),
|
||||
MessageElementFlag::Text, MessageColor::Text);
|
||||
builder.emplace<TextElement>(text, MessageElementFlag::Text,
|
||||
MessageColor::Text);
|
||||
builder.message().messageText = text;
|
||||
builder.message().displayName = this->userName_;
|
||||
messages.push_back(builder.release());
|
||||
}
|
||||
}
|
||||
|
@ -191,4 +190,5 @@ void LogsPopup::getOverrustleLogs()
|
|||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,37 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include "widgets/BaseWindow.hpp"
|
||||
#include "widgets/helper/SearchPopup.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
class ChannelView;
|
||||
|
||||
class Channel;
|
||||
using ChannelPtr = std::shared_ptr<Channel>;
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
class LogsPopup : public BaseWindow
|
||||
class LogsPopup : public SearchPopup
|
||||
{
|
||||
public:
|
||||
LogsPopup();
|
||||
|
||||
void setChannelName(QString channelName);
|
||||
void setChannel(std::shared_ptr<Channel> channel);
|
||||
void setTargetUserName(QString userName);
|
||||
void setChannel(const ChannelPtr &channel) override;
|
||||
void setChannelName(const QString &channelName);
|
||||
void setTargetUserName(const QString &userName);
|
||||
|
||||
void getLogs();
|
||||
|
||||
protected:
|
||||
void updateWindowTitle() override;
|
||||
|
||||
private:
|
||||
ChannelView *channelView_ = nullptr;
|
||||
ChannelPtr channel_;
|
||||
|
||||
QString userName_;
|
||||
QString channelName_;
|
||||
|
||||
void initLayout();
|
||||
void setMessages(std::vector<MessagePtr> &messages);
|
||||
void getOverrustleLogs();
|
||||
void getLogviewerLogs(const QString &roomID);
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
namespace chatterino {
|
||||
|
||||
NotificationPopup::NotificationPopup()
|
||||
: BaseWindow((QWidget *)nullptr, BaseWindow::Frameless)
|
||||
: BaseWindow(BaseWindow::Frameless)
|
||||
, channel_(std::make_shared<Channel>("notifications", Channel::Type::None))
|
||||
|
||||
{
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace chatterino {
|
|||
SettingsDialog *SettingsDialog::handle = nullptr;
|
||||
|
||||
SettingsDialog::SettingsDialog()
|
||||
: BaseWindow(nullptr, BaseWindow::DisableCustomScaling)
|
||||
: BaseWindow(BaseWindow::DisableCustomScaling)
|
||||
{
|
||||
this->setWindowTitle("Chatterino Settings");
|
||||
|
||||
|
@ -41,7 +41,7 @@ SettingsDialog::SettingsDialog()
|
|||
|
||||
void SettingsDialog::initUi()
|
||||
{
|
||||
auto outerBox = LayoutCreator<SettingsDialog>(this)
|
||||
auto outerBox = LayoutCreator<QWidget>(this->getLayoutContainer())
|
||||
.setLayoutType<QVBoxLayout>()
|
||||
.withoutSpacing();
|
||||
|
||||
|
|
|
@ -11,9 +11,8 @@
|
|||
namespace chatterino {
|
||||
|
||||
UpdateDialog::UpdateDialog()
|
||||
: BaseWindow(nullptr,
|
||||
BaseWindow::Flags(BaseWindow::Frameless | BaseWindow::TopMost |
|
||||
BaseWindow::EnableCustomFrame))
|
||||
: BaseWindow({BaseWindow::Frameless, BaseWindow::TopMost,
|
||||
BaseWindow::EnableCustomFrame})
|
||||
{
|
||||
auto layout =
|
||||
LayoutCreator<UpdateDialog>(this).setLayoutType<QVBoxLayout>();
|
||||
|
|
|
@ -43,8 +43,7 @@ namespace {
|
|||
} // namespace
|
||||
|
||||
UserInfoPopup::UserInfoPopup()
|
||||
: BaseWindow(nullptr, BaseWindow::Flags(BaseWindow::Frameless |
|
||||
BaseWindow::FramelessDraggable))
|
||||
: BaseWindow({BaseWindow::Frameless, BaseWindow::FramelessDraggable})
|
||||
, hack_(new bool)
|
||||
{
|
||||
this->setStayInScreenRect(true);
|
||||
|
@ -55,8 +54,8 @@ UserInfoPopup::UserInfoPopup()
|
|||
|
||||
auto app = getApp();
|
||||
|
||||
auto layout =
|
||||
LayoutCreator<UserInfoPopup>(this).setLayoutType<QVBoxLayout>();
|
||||
auto layout = LayoutCreator<QWidget>(this->getLayoutContainer())
|
||||
.setLayoutType<QVBoxLayout>();
|
||||
|
||||
// first line
|
||||
auto head = layout.emplace<QHBoxLayout>().withoutMargin();
|
||||
|
@ -223,26 +222,32 @@ UserInfoPopup::UserInfoPopup()
|
|||
});
|
||||
}
|
||||
|
||||
// this->setStyleSheet("font-size: 11pt;");
|
||||
|
||||
this->installEvents();
|
||||
|
||||
this->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Policy::Ignored);
|
||||
}
|
||||
|
||||
void UserInfoPopup::themeChangedEvent()
|
||||
{
|
||||
BaseWindow::themeChangedEvent();
|
||||
|
||||
this->setStyleSheet(
|
||||
"background: #333; font-size: " +
|
||||
QString::number(getFonts()
|
||||
->getFont(FontStyle::UiMediumBold, this->scale())
|
||||
.pixelSize()) +
|
||||
"px;");
|
||||
for (auto &&child : this->findChildren<QCheckBox *>())
|
||||
{
|
||||
child->setFont(getFonts()->getFont(FontStyle::UiMedium, this->scale()));
|
||||
}
|
||||
}
|
||||
|
||||
void UserInfoPopup::scaleChangedEvent(float /*scale*/)
|
||||
{
|
||||
themeChangedEvent();
|
||||
|
||||
QTimer::singleShot(20, this, [this] {
|
||||
auto geo = this->geometry();
|
||||
geo.setWidth(10);
|
||||
geo.setHeight(10);
|
||||
|
||||
this->setGeometry(geo);
|
||||
});
|
||||
}
|
||||
|
||||
void UserInfoPopup::installEvents()
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
namespace chatterino {
|
||||
|
||||
WelcomeDialog::WelcomeDialog()
|
||||
: BaseWindow(nullptr, BaseWindow::EnableCustomFrame)
|
||||
: BaseWindow(BaseWindow::EnableCustomFrame)
|
||||
{
|
||||
this->setWindowTitle("Chatterino quick setup");
|
||||
}
|
||||
|
|
|
@ -133,6 +133,8 @@ ChannelView::ChannelView(BaseWidget *parent)
|
|||
this->clickTimer_ = new QTimer(this);
|
||||
this->clickTimer_->setSingleShot(true);
|
||||
this->clickTimer_->setInterval(500);
|
||||
|
||||
this->setFocusPolicy(Qt::FocusPolicy::StrongFocus);
|
||||
}
|
||||
|
||||
void ChannelView::initializeLayout()
|
||||
|
@ -1526,14 +1528,8 @@ void ChannelView::addContextMenuItems(
|
|||
QString url = hoveredElement->getLink().value;
|
||||
|
||||
// open link
|
||||
bool incognitoByDefault = supportsIncognitoLinks() &&
|
||||
layout->getMessage()->loginName == "hemirt";
|
||||
menu->addAction("Open link", [url, incognitoByDefault] {
|
||||
if (incognitoByDefault)
|
||||
openLinkIncognito(url);
|
||||
else
|
||||
QDesktopServices::openUrl(QUrl(url));
|
||||
});
|
||||
menu->addAction("Open link",
|
||||
[url] { QDesktopServices::openUrl(QUrl(url)); });
|
||||
// open link default
|
||||
if (supportsIncognitoLinks())
|
||||
{
|
||||
|
@ -1699,6 +1695,9 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
|
|||
|
||||
case Link::Url:
|
||||
{
|
||||
if (getSettings()->openLinksIncognito && supportsIncognitoLinks())
|
||||
openLinkIncognito(link.value);
|
||||
else
|
||||
QDesktopServices::openUrl(QUrl(link.value));
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -7,23 +7,70 @@
|
|||
|
||||
#include "common/Channel.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"
|
||||
|
||||
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()
|
||||
{
|
||||
this->initLayout();
|
||||
this->resize(400, 600);
|
||||
}
|
||||
|
||||
void SearchPopup::setChannel(ChannelPtr channel)
|
||||
void SearchPopup::setChannel(const ChannelPtr &channel)
|
||||
{
|
||||
this->channelName_ = channel->getName();
|
||||
this->snapshot_ = channel->getMessageSnapshot();
|
||||
this->performSearch();
|
||||
this->search();
|
||||
|
||||
this->setWindowTitle("Searching in " + channel->getName() + "s history");
|
||||
this->updateWindowTitle();
|
||||
}
|
||||
|
||||
void SearchPopup::updateWindowTitle()
|
||||
{
|
||||
this->setWindowTitle("Searching in " + this->channelName_ + "s history");
|
||||
}
|
||||
|
||||
void SearchPopup::search()
|
||||
{
|
||||
this->channelView_->setChannel(filter(this->searchInput_->text(),
|
||||
this->channelName_, this->snapshot_));
|
||||
}
|
||||
|
||||
void SearchPopup::keyPressEvent(QKeyEvent *e)
|
||||
|
@ -43,18 +90,20 @@ void SearchPopup::initLayout()
|
|||
{
|
||||
QVBoxLayout *layout1 = new QVBoxLayout(this);
|
||||
layout1->setMargin(0);
|
||||
layout1->setSpacing(0);
|
||||
|
||||
// HBOX
|
||||
{
|
||||
QHBoxLayout *layout2 = new QHBoxLayout(this);
|
||||
layout2->setMargin(6);
|
||||
layout2->setMargin(8);
|
||||
layout2->setSpacing(8);
|
||||
|
||||
// SEARCH INPUT
|
||||
{
|
||||
this->searchInput_ = new QLineEdit(this);
|
||||
layout2->addWidget(this->searchInput_);
|
||||
QObject::connect(this->searchInput_, &QLineEdit::returnPressed,
|
||||
[this] { this->performSearch(); });
|
||||
[this] { this->search(); });
|
||||
}
|
||||
|
||||
// SEARCH BUTTON
|
||||
|
@ -63,7 +112,7 @@ void SearchPopup::initLayout()
|
|||
searchButton->setText("Search");
|
||||
layout2->addWidget(searchButton);
|
||||
QObject::connect(searchButton, &QPushButton::clicked,
|
||||
[this] { this->performSearch(); });
|
||||
[this] { this->search(); });
|
||||
}
|
||||
|
||||
layout1->addLayout(layout2);
|
||||
|
@ -80,25 +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 (text.isEmpty() ||
|
||||
message->searchText.indexOf(this->searchInput_->text(), 0,
|
||||
Qt::CaseInsensitive) != -1)
|
||||
if (auto match = predicateRegex.match(*it); match.hasMatch())
|
||||
{
|
||||
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
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "ForwardDecl.hpp"
|
||||
#include "messages/LimitedQueueSnapshot.hpp"
|
||||
#include "messages/search/MessagePredicate.hpp"
|
||||
#include "widgets/BaseWindow.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
@ -9,30 +11,50 @@ class QLineEdit;
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
class ChannelView;
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
class SearchPopup : public BaseWindow
|
||||
{
|
||||
public:
|
||||
SearchPopup();
|
||||
|
||||
void setChannel(std::shared_ptr<Channel> channel);
|
||||
virtual void setChannel(const ChannelPtr &channel);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
|
||||
virtual void updateWindowTitle();
|
||||
|
||||
private:
|
||||
void initLayout();
|
||||
void performSearch();
|
||||
void search();
|
||||
|
||||
/**
|
||||
* @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_;
|
||||
QLineEdit *searchInput_;
|
||||
ChannelView *channelView_;
|
||||
QString channelName_;
|
||||
QLineEdit *searchInput_{};
|
||||
ChannelView *channelView_{};
|
||||
QString channelName_{};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/FuzzyConvert.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/IncognitoBrowser.hpp"
|
||||
#include "widgets/BaseWindow.hpp"
|
||||
#include "widgets/helper/Line.hpp"
|
||||
|
||||
|
@ -377,6 +378,12 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
|||
|
||||
layout.addTitle("Miscellaneous");
|
||||
|
||||
if (supportsIncognitoLinks())
|
||||
{
|
||||
layout.addCheckbox("Open links in incognito/private mode",
|
||||
s.openLinksIncognito);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
if (!getPaths()->isPortable())
|
||||
{
|
||||
|
|
|
@ -57,7 +57,7 @@ HighlightingPage::HighlightingPage()
|
|||
|
||||
view->addRegexHelpLink();
|
||||
view->setTitles({"Pattern", "Flash\ntaskbar", "Play\nsound",
|
||||
"Enable\nregex"});
|
||||
"Enable\nregex", "Case-\nsensitive"});
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
QHeaderView::Fixed);
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
|
@ -71,8 +71,8 @@ HighlightingPage::HighlightingPage()
|
|||
});
|
||||
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()->highlights->phrases.appendItem(
|
||||
HighlightPhrase{"my phrase", true, false, false});
|
||||
getApp()->highlights->phrases.appendItem(HighlightPhrase{
|
||||
"my phrase", true, false, false, false});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -87,6 +87,10 @@ HighlightingPage::HighlightingPage()
|
|||
.getElement();
|
||||
|
||||
view->addRegexHelpLink();
|
||||
view->getTableView()->horizontalHeader()->hideSection(4);
|
||||
|
||||
// Case-sensitivity doesn't make sense for user names so it is
|
||||
// set to "false" by default & no checkbox is shown
|
||||
view->setTitles({"Username", "Flash\ntaskbar", "Play\nsound",
|
||||
"Enable\nregex"});
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
|
@ -103,7 +107,7 @@ HighlightingPage::HighlightingPage()
|
|||
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()->highlights->highlightedUsers.appendItem(
|
||||
HighlightPhrase{"highlighted user", true, false,
|
||||
HighlightPhrase{"highlighted user", true, false, false,
|
||||
false});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ namespace {
|
|||
const QString &title, const QString &description)
|
||||
{
|
||||
auto window =
|
||||
new BaseWindow(parent, BaseWindow::Flags::EnableCustomFrame);
|
||||
new BaseWindow(BaseWindow::Flags::EnableCustomFrame, parent);
|
||||
window->setWindowTitle("Chatterino - " + title);
|
||||
window->setAttribute(Qt::WA_DeleteOnClose);
|
||||
auto layout = new QVBoxLayout();
|
||||
|
@ -89,6 +89,7 @@ Split::Split(QWidget *parent)
|
|||
{
|
||||
this->setMouseTracking(true);
|
||||
this->view_->setPausable(true);
|
||||
this->view_->setFocusPolicy(Qt::FocusPolicy::NoFocus);
|
||||
|
||||
this->vbox_->setSpacing(0);
|
||||
this->vbox_->setMargin(1);
|
||||
|
|
Loading…
Reference in a new issue