Merge branch 'master' into logging

This commit is contained in:
pajlada
2018-06-05 13:16:20 +02:00
committed by GitHub
172 changed files with 5997 additions and 3348 deletions
+12 -61
View File
@@ -1,11 +1,12 @@
#include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "controllers/commands/commandcontroller.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "controllers/ignores/ignorecontroller.hpp"
#include "controllers/taggedusers/taggeduserscontroller.hpp"
#include "providers/twitch/pubsub.hpp"
#include "providers/twitch/twitchserver.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/fontmanager.hpp"
#include "singletons/loggingmanager.hpp"
@@ -19,12 +20,6 @@
#include <atomic>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#endif
using namespace chatterino::singletons;
namespace chatterino {
@@ -34,7 +29,7 @@ namespace {
bool isBigEndian()
{
int test = 1;
char *p = (char *)&test;
char *p = reinterpret_cast<char *>(&test);
return p[0] == 0;
}
@@ -61,6 +56,7 @@ void Application::construct()
isAppConstructed = true;
// 1. Instantiate all classes
this->settings = new singletons::SettingManager;
this->paths = new singletons::PathManager(this->argc, this->argv);
this->themes = new singletons::ThemeManager;
this->windows = new singletons::WindowManager;
@@ -68,9 +64,9 @@ void Application::construct()
this->commands = new controllers::commands::CommandController;
this->highlights = new controllers::highlights::HighlightController;
this->ignores = new controllers::ignores::IgnoreController;
this->accounts = new singletons::AccountManager;
this->taggedUsers = new controllers::taggedusers::TaggedUsersController;
this->accounts = new controllers::accounts::AccountController;
this->emotes = new singletons::EmoteManager;
this->settings = new singletons::SettingManager;
this->fonts = new singletons::FontManager;
this->resources = new singletons::ResourceManager;
@@ -92,12 +88,13 @@ void Application::initialize()
// 2. Initialize/load classes
this->settings->initialize();
this->windows->initialize();
this->nativeMessaging->registerHost();
this->settings->load();
this->commands->load();
this->logging->initialize();
this->windows->initialize();
this->resources->initialize();
@@ -109,7 +106,6 @@ void Application::initialize()
this->accounts->load();
this->twitch.server->initialize();
this->logging->initialize();
// XXX
this->settings->updateWordTypeMask();
@@ -182,8 +178,9 @@ void Application::initialize()
}
auto msg = messages::Message::createTimeoutMessage(action);
msg->flags |= messages::Message::PubSub;
util::postToThread([chan, msg] { chan->addMessage(msg); });
util::postToThread([chan, msg] { chan->addOrReplaceTimeout(msg); });
});
this->twitch.pubsub->sig.moderation.userUnbanned.connect([&](const auto &action) {
@@ -205,10 +202,10 @@ void Application::initialize()
// TODO(pajlada): Unlisten to all authed topics instead of only moderation topics
// this->twitch.pubsub->UnlistenAllAuthedTopics();
this->twitch.pubsub->listenToWhispers(this->accounts->Twitch.getCurrent()); //
this->twitch.pubsub->listenToWhispers(this->accounts->twitch.getCurrent()); //
};
this->accounts->Twitch.currentUserChanged.connect(RequestModerationActions);
this->accounts->twitch.currentUserChanged.connect(RequestModerationActions);
RequestModerationActions();
}
@@ -231,52 +228,6 @@ void Application::save()
this->commands->save();
}
void Application::runNativeMessagingHost()
{
auto app = getApp();
app->nativeMessaging = new singletons::NativeMessagingManager;
#ifdef Q_OS_WIN
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
#endif
#if 0
bool bigEndian = isBigEndian();
#endif
while (true) {
char size_c[4];
std::cin.read(size_c, 4);
if (std::cin.eof()) {
break;
}
uint32_t size = *reinterpret_cast<uint32_t *>(size_c);
#if 0
// To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead
uint32_t size = 0;
if (bigEndian) {
size = size_c[3] | static_cast<uint32_t>(size_c[2]) << 8 |
static_cast<uint32_t>(size_c[1]) << 16 | static_cast<uint32_t>(size_c[0]) << 24;
} else {
size = size_c[0] | static_cast<uint32_t>(size_c[1]) << 8 |
static_cast<uint32_t>(size_c[2]) << 16 | static_cast<uint32_t>(size_c[3]) << 24;
}
#endif
char *b = (char *)malloc(size + 1);
std::cin.read(b, size);
*(b + size) = '\0';
app->nativeMessaging->sendToGuiProcess(QByteArray(b, size));
free(b);
}
}
Application *getApp()
{
assert(staticApp != nullptr);
+8 -1
View File
@@ -26,6 +26,12 @@ class HighlightController;
namespace ignores {
class IgnoreController;
}
namespace taggedusers {
class TaggedUsersController;
}
namespace accounts {
class AccountController;
}
} // namespace controllers
namespace singletons {
@@ -67,7 +73,8 @@ public:
controllers::commands::CommandController *commands = nullptr;
controllers::highlights::HighlightController *highlights = nullptr;
controllers::ignores::IgnoreController *ignores = nullptr;
singletons::AccountManager *accounts = nullptr;
controllers::taggedusers::TaggedUsersController *taggedUsers = nullptr;
controllers::accounts::AccountController *accounts = nullptr;
singletons::EmoteManager *emotes = nullptr;
singletons::NativeMessagingManager *nativeMessaging = nullptr;
singletons::SettingManager *settings = nullptr;
+76 -47
View File
@@ -45,6 +45,11 @@ Channel::Type Channel::getType() const
return this->type;
}
bool Channel::isTwitchChannel() const
{
return this->type >= Twitch && this->type < TwitchEnd;
}
bool Channel::isEmpty() const
{
return this->name.isEmpty();
@@ -60,57 +65,14 @@ void Channel::addMessage(MessagePtr message)
auto app = getApp();
MessagePtr deleted;
bool isTimeout = (message->flags & Message::Timeout) != 0;
if (!isTimeout) {
const QString &username = message->loginName;
if (!username.isEmpty()) {
// TODO: Add recent chatters display name. This should maybe be a setting
this->addRecentChatter(message);
}
const QString &username = message->loginName;
if (!username.isEmpty()) {
// TODO: Add recent chatters display name. This should maybe be a setting
this->addRecentChatter(message);
}
app->logging->addMessage(this->name, message);
if (isTimeout) {
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
bool addMessage = true;
int snapshotLength = snapshot.getLength();
int end = std::max(0, snapshotLength - 20);
for (int i = snapshotLength - 1; i >= end; --i) {
auto &s = snapshot[i];
if (s->flags.HasFlag(Message::Untimeout) && s->timeoutUser == message->timeoutUser) {
break;
}
if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == message->timeoutUser) {
assert(message->banAction != nullptr);
MessagePtr replacement(
Message::createTimeoutMessage(*(message->banAction), s->count + 1));
this->replaceMessage(s, replacement);
addMessage = false;
}
}
// disable the messages from the user
for (int i = 0; i < snapshotLength; i++) {
auto &s = snapshot[i];
if ((s->flags & (Message::Timeout | Message::Untimeout)) == 0 &&
s->loginName == message->timeoutUser) {
s->flags.EnableFlag(Message::Disabled);
}
}
// XXX: Might need the following line
// WindowManager::getInstance().repaintVisibleChatWidgets(this);
if (!addMessage) {
return;
}
}
if (this->messages.pushBack(message, deleted)) {
this->messageRemovedFromStart.invoke(deleted);
}
@@ -118,6 +80,73 @@ void Channel::addMessage(MessagePtr message)
this->messageAppended.invoke(message);
}
void Channel::addOrReplaceTimeout(messages::MessagePtr message)
{
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
int snapshotLength = snapshot.getLength();
int end = std::max(0, snapshotLength - 20);
bool addMessage = true;
QTime minimumTime = QTime::currentTime().addSecs(-5);
for (int i = snapshotLength - 1; i >= end; --i) {
auto &s = snapshot[i];
qDebug() << s->parseTime << minimumTime;
if (s->parseTime < minimumTime) {
break;
}
if (s->flags.HasFlag(Message::Untimeout) && s->timeoutUser == message->timeoutUser) {
break;
}
if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == message->timeoutUser) {
if (message->flags.HasFlag(Message::PubSub) && !s->flags.HasFlag(Message::PubSub)) {
this->replaceMessage(s, message);
addMessage = false;
break;
}
if (!message->flags.HasFlag(Message::PubSub) && s->flags.HasFlag(Message::PubSub)) {
addMessage = false;
break;
}
int count = s->count + 1;
messages::MessagePtr replacement(Message::createSystemMessage(
message->searchText + QString("(") + QString::number(count) + " times)"));
replacement->timeoutUser = message->timeoutUser;
replacement->count = count;
replacement->flags = message->flags;
this->replaceMessage(s, replacement);
return;
}
}
// disable the messages from the user
for (int i = 0; i < snapshotLength; i++) {
auto &s = snapshot[i];
if ((s->flags & (Message::Timeout | Message::Untimeout)) == 0 &&
s->loginName == message->timeoutUser) {
s->flags.EnableFlag(Message::Disabled);
}
}
if (addMessage) {
this->addMessage(message);
}
// XXX: Might need the following line
// WindowManager::getInstance().repaintVisibleChatWidgets(this);
}
void Channel::addMessagesAtStart(std::vector<messages::MessagePtr> &_messages)
{
std::vector<messages::MessagePtr> addedMessages = this->messages.pushFront(_messages);
+3
View File
@@ -29,6 +29,7 @@ public:
TwitchWhispers,
TwitchWatching,
TwitchMentions,
TwitchEnd,
};
explicit Channel(const QString &_name, Type type);
@@ -43,11 +44,13 @@ public:
pajlada::Signals::NoArgSignal destroyed;
Type getType() const;
bool isTwitchChannel() const;
virtual bool isEmpty() const;
messages::LimitedQueueSnapshot<messages::MessagePtr> getMessageSnapshot();
void addMessage(messages::MessagePtr message);
void addMessagesAtStart(std::vector<messages::MessagePtr> &messages);
void addOrReplaceTimeout(messages::MessagePtr message);
void replaceMessage(messages::MessagePtr message, messages::MessagePtr replacement);
virtual void addRecentChatter(const std::shared_ptr<messages::Message> &message);
+5
View File
@@ -3,6 +3,7 @@
#include "debug/log.hpp"
#include <QString>
#include <QWidget>
#include <boost/preprocessor.hpp>
#include <string>
@@ -20,4 +21,8 @@ inline QString qS(const std::string &string)
return QString::fromStdString(string);
}
const Qt::KeyboardModifiers showSplitOverlayModifiers = Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showAddSplitRegions = Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
} // namespace chatterino
+22 -9
View File
@@ -1,11 +1,23 @@
#include "account.hpp"
#include <tuple>
namespace chatterino {
namespace controllers {
namespace accounts {
Account::Account(const QString &category)
Account::Account(ProviderId _providerId)
: providerId(_providerId)
{
static QString twitch("Twitch");
this->category = [&]() {
switch (_providerId) {
case ProviderId::Twitch:
return twitch;
}
return QString("Unknown ProviderId");
}();
}
const QString &Account::getCategory() const
@@ -13,16 +25,17 @@ const QString &Account::getCategory() const
return this->category;
}
ProviderId Account::getProviderId() const
{
return this->providerId;
}
bool Account::operator<(const Account &other) const
{
if (this->category < other.category) {
return true;
} else if (this->category == other.category) {
if (this->toString() < other.toString()) {
return true;
}
}
return false;
QString a = this->toString();
QString b = other.toString();
return std::tie(this->category, a) < std::tie(other.category, b);
}
} // namespace accounts
+6 -1
View File
@@ -1,5 +1,7 @@
#pragma once
#include "providerid.hpp"
#include <QString>
namespace chatterino {
@@ -9,14 +11,17 @@ namespace accounts {
class Account
{
public:
Account(const QString &category);
Account(ProviderId providerId);
virtual ~Account() = default;
virtual QString toString() const = 0;
const QString &getCategory() const;
ProviderId getProviderId() const;
bool operator<(const Account &other) const;
private:
ProviderId providerId;
QString category;
};
+30 -1
View File
@@ -8,13 +8,42 @@ namespace accounts {
AccountController::AccountController()
{
this->twitch.accounts.itemInserted.connect([this](const auto &args) {
this->accounts.insertItem(std::dynamic_pointer_cast<Account>(args.item));
});
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
if (args.caller != this) {
auto &accs = this->twitch.accounts.getVector();
auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end());
this->accounts.removeItem(it - accs.begin());
}
});
this->accounts.itemRemoved.connect([this](const auto &args) {
switch (args.item->getProviderId()) {
case ProviderId::Twitch: {
auto &accs = this->twitch.accounts.getVector();
auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end());
this->twitch.accounts.removeItem(it - accs.begin(), this);
} break;
}
});
}
void AccountController::load()
{
this->twitch.load();
}
AccountModel *AccountController::createModel(QObject *parent)
{
AccountModel *model = new AccountModel(parent);
//(util::BaseSignalVector<stdAccount> *)
model->init(&this->accounts);
return model;
}
@@ -3,6 +3,7 @@
#include <QObject>
#include "controllers/accounts/account.hpp"
#include "providers/twitch/twitchaccountmanager.hpp"
#include "util/sharedptrelementless.hpp"
#include "util/signalvector2.hpp"
@@ -19,6 +20,10 @@ public:
AccountModel *createModel(QObject *parent);
void load();
providers::twitch::TwitchAccountManager twitch;
private:
util::SortedSignalVector<std::shared_ptr<Account>, util::SharedPtrElementLess<Account>>
accounts;
+38 -3
View File
@@ -1,5 +1,7 @@
#include "accountmodel.hpp"
#include "util/standarditemhelper.hpp"
namespace chatterino {
namespace controllers {
namespace accounts {
@@ -10,16 +12,49 @@ AccountModel::AccountModel(QObject *parent)
}
// turn a vector item into a model row
std::shared_ptr<Account> AccountModel::getItemFromRow(std::vector<QStandardItem *> &row)
std::shared_ptr<Account> AccountModel::getItemFromRow(std::vector<QStandardItem *> &,
const std::shared_ptr<Account> &original)
{
return nullptr;
return original;
}
// turns a row in the model into a vector item
void AccountModel::getRowFromItem(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row)
{
row[0]->setData(item->toString(), Qt::DisplayRole);
util::setStringItem(row[0], item->toString(), false);
row[0]->setData(QFont("Segoe UI", 10), Qt::FontRole);
}
int AccountModel::beforeInsert(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int proposedIndex)
{
if (this->categoryCount[item->getCategory()]++ == 0) {
auto row = this->createRow();
util::setStringItem(row[0], item->getCategory(), false, false);
row[0]->setData(QFont("Segoe UI Light", 16), Qt::FontRole);
this->insertCustomRow(std::move(row), proposedIndex);
return proposedIndex + 1;
}
return proposedIndex;
}
void AccountModel::afterRemoved(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int index)
{
auto it = this->categoryCount.find(item->getCategory());
assert(it != this->categoryCount.end());
if (it->second <= 1) {
this->categoryCount.erase(it);
this->removeCustomRow(index - 1);
} else {
it->second--;
}
}
} // namespace accounts
+14 -1
View File
@@ -1,8 +1,11 @@
#pragma once
#include "controllers/accounts/account.hpp"
#include "util/qstringhash.hpp"
#include "util/signalvectormodel.hpp"
#include <unordered_map>
namespace chatterino {
namespace controllers {
namespace accounts {
@@ -16,13 +19,23 @@ public:
protected:
// turn a vector item into a model row
virtual std::shared_ptr<Account> getItemFromRow(std::vector<QStandardItem *> &row) override;
virtual std::shared_ptr<Account> getItemFromRow(
std::vector<QStandardItem *> &row, const std::shared_ptr<Account> &original) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row) override;
virtual int beforeInsert(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int proposedIndex) override;
virtual void afterRemoved(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int index) override;
friend class AccountController;
private:
std::unordered_map<QString, int> categoryCount;
};
} // namespace accounts
@@ -1,12 +1,12 @@
#include "commandcontroller.hpp"
#include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "controllers/commands/command.hpp"
#include "controllers/commands/commandmodel.hpp"
#include "messages/messagebuilder.hpp"
#include "providers/twitch/twitchchannel.hpp"
#include "providers/twitch/twitchserver.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/pathmanager.hpp"
#include "util/signalvector2.hpp"
@@ -110,7 +110,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
return "";
} else if (commandName == "/uptime") {
const auto &streamStatus = twitchChannel->GetStreamStatus();
const auto &streamStatus = twitchChannel->getStreamStatus();
QString messageText =
streamStatus.live ? streamStatus.uptime : "Channel is not live.";
@@ -121,7 +121,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
} else if (commandName == "/ignore" && words.size() >= 2) {
auto app = getApp();
auto user = app->accounts->Twitch.getCurrent();
auto user = app->accounts->twitch.getCurrent();
auto target = words.at(1);
if (user->isAnon()) {
@@ -138,7 +138,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
} else if (commandName == "/unignore" && words.size() >= 2) {
auto app = getApp();
auto user = app->accounts->Twitch.getCurrent();
auto user = app->accounts->twitch.getCurrent();
auto target = words.at(1);
if (user->isAnon()) {
@@ -161,7 +161,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
messages::MessageBuilder b;
b.emplace<messages::TextElement>(app->accounts->Twitch.getCurrent()->getUserName(),
b.emplace<messages::TextElement>(app->accounts->twitch.getCurrent()->getUserName(),
messages::MessageElement::Text);
b.emplace<messages::TextElement>("->", messages::MessageElement::Text);
b.emplace<messages::TextElement>(words[1], messages::MessageElement::Text);
@@ -169,7 +169,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
QString rest = "";
for (int i = 2; i < words.length(); i++) {
rest += words[i];
rest += words[i] + " ";
}
b.emplace<messages::TextElement>(rest, messages::MessageElement::Text);
+1 -1
View File
@@ -11,7 +11,7 @@ CommandModel::CommandModel(QObject *parent)
}
// turn a vector item into a model row
Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row)
Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row, const Command &original)
{
return Command(row[0]->data(Qt::EditRole).toString(), row[1]->data(Qt::EditRole).toString());
}
+2 -1
View File
@@ -17,7 +17,8 @@ class CommandModel : public util::SignalVectorModel<Command>
protected:
// turn a vector item into a model row
virtual Command getItemFromRow(std::vector<QStandardItem *> &row) override;
virtual Command getItemFromRow(std::vector<QStandardItem *> &row,
const Command &command) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const Command &item, std::vector<QStandardItem *> &row) override;
@@ -2,6 +2,7 @@
#include "application.hpp"
#include "controllers/highlights/highlightmodel.hpp"
#include "widgets/notificationpopup.hpp"
namespace chatterino {
namespace controllers {
@@ -21,7 +22,6 @@ void HighlightController::initialize()
}
this->phrases.delayedItemsChanged.connect([this] { //
int xd = this->phrases.getVector().size();
this->highlightsSetting.setValue(this->phrases.getVector());
});
}
@@ -34,6 +34,15 @@ HighlightModel *HighlightController::createModel(QObject *parent)
return model;
}
void HighlightController::addHighlight(const messages::MessagePtr &msg)
{
// static widgets::NotificationPopup popup;
// popup.updatePosition();
// popup.addMessage(msg);
// popup.show();
}
} // namespace highlights
} // namespace controllers
} // namespace chatterino
@@ -1,6 +1,7 @@
#pragma once
#include "controllers/highlights/highlightphrase.hpp"
#include "messages/message.hpp"
#include "singletons/settingsmanager.hpp"
#include "util/signalvector2.hpp"
@@ -21,6 +22,8 @@ public:
HighlightModel *createModel(QObject *parent);
void addHighlight(const messages::MessagePtr &msg);
private:
bool initialized = false;
@@ -15,7 +15,8 @@ HighlightModel::HighlightModel(QObject *parent)
}
// turn a vector item into a model row
HighlightPhrase HighlightModel::getItemFromRow(std::vector<QStandardItem *> &row)
HighlightPhrase HighlightModel::getItemFromRow(std::vector<QStandardItem *> &row,
const HighlightPhrase &original)
{
// key, alert, sound, regex
@@ -17,7 +17,8 @@ class HighlightModel : public util::SignalVectorModel<HighlightPhrase>
protected:
// turn a vector item into a model row
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row) override;
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row,
const HighlightPhrase &original) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const HighlightPhrase &item,
+2 -1
View File
@@ -15,7 +15,8 @@ IgnoreModel::IgnoreModel(QObject *parent)
}
// turn a vector item into a model row
IgnorePhrase IgnoreModel::getItemFromRow(std::vector<QStandardItem *> &row)
IgnorePhrase IgnoreModel::getItemFromRow(std::vector<QStandardItem *> &row,
const IgnorePhrase &original)
{
// key, regex
+2 -1
View File
@@ -17,7 +17,8 @@ class IgnoreModel : public util::SignalVectorModel<IgnorePhrase>
protected:
// turn a vector item into a model row
virtual IgnorePhrase getItemFromRow(std::vector<QStandardItem *> &row) override;
virtual IgnorePhrase getItemFromRow(std::vector<QStandardItem *> &row,
const IgnorePhrase &original) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const IgnorePhrase &item,
@@ -0,0 +1,24 @@
#include "taggeduser.hpp"
#include <tuple>
namespace chatterino {
namespace controllers {
namespace taggedusers {
TaggedUser::TaggedUser(ProviderId _provider, const QString &_name, const QString &_id)
: provider(_provider)
, name(_name)
, id(_id)
{
}
bool TaggedUser::operator<(const TaggedUser &other) const
{
return std::tie(this->provider, this->name, this->id) <
std::tie(other.provider, other.name, other.id);
}
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino
@@ -0,0 +1,24 @@
#pragma once
#include <QString>
#include <providerid.hpp>
namespace chatterino {
namespace controllers {
namespace taggedusers {
class TaggedUser
{
public:
TaggedUser(ProviderId provider, const QString &name, const QString &id);
bool operator<(const TaggedUser &other) const;
ProviderId provider;
QString name;
QString id;
};
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino
@@ -0,0 +1,23 @@
#include "taggeduserscontroller.hpp"
#include "controllers/taggedusers/taggedusersmodel.hpp"
namespace chatterino {
namespace controllers {
namespace taggedusers {
TaggedUsersController::TaggedUsersController()
{
}
TaggedUsersModel *TaggedUsersController::createModel(QObject *parent)
{
TaggedUsersModel *model = new TaggedUsersModel(parent);
model->init(&this->users);
return model;
}
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino
@@ -0,0 +1,24 @@
#pragma once
#include "controllers/taggedusers/taggeduser.hpp"
#include "util/signalvector2.hpp"
namespace chatterino {
namespace controllers {
namespace taggedusers {
class TaggedUsersModel;
class TaggedUsersController
{
public:
TaggedUsersController();
util::SortedSignalVector<TaggedUser, std::less<TaggedUser>> users;
TaggedUsersModel *createModel(QObject *parent = nullptr);
};
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino
@@ -0,0 +1,66 @@
#include "taggedusersmodel.hpp"
#include "application.hpp"
#include "util/standarditemhelper.hpp"
namespace chatterino {
namespace controllers {
namespace taggedusers {
// commandmodel
TaggedUsersModel::TaggedUsersModel(QObject *parent)
: util::SignalVectorModel<TaggedUser>(1, parent)
{
}
// turn a vector item into a model row
TaggedUser TaggedUsersModel::getItemFromRow(std::vector<QStandardItem *> &row,
const TaggedUser &original)
{
return original;
}
// turns a row in the model into a vector item
void TaggedUsersModel::getRowFromItem(const TaggedUser &item, std::vector<QStandardItem *> &row)
{
util::setStringItem(row[0], item.name);
}
void TaggedUsersModel::afterInit()
{
// std::vector<QStandardItem *> row = this->createRow();
// util::setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(), true,
// false); row[0]->setData("Your username (automatic)", Qt::DisplayRole);
// util::setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(), true,
// false); util::setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(),
// true, false); row[3]->setFlags(0); this->insertCustomRow(row, 0);
}
// void TaggedUserModel::customRowSetData(const std::vector<QStandardItem *> &row, int column,
// const QVariant &value, int role)
//{
// switch (column) {
// case 0: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightsSelf.setValue(value.toBool());
// }
// } break;
// case 1: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightTaskbar.setValue(value.toBool());
// }
// } break;
// case 2: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightSound.setValue(value.toBool());
// }
// } break;
// case 3: {
// // empty element
// } break;
// }
//}
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino
@@ -0,0 +1,34 @@
#pragma once
#include "controllers/taggedusers/taggeduser.hpp"
#include "util/signalvectormodel.hpp"
namespace chatterino {
namespace controllers {
namespace taggedusers {
class TaggedUsersController;
class TaggedUsersModel : public util::SignalVectorModel<TaggedUser>
{
explicit TaggedUsersModel(QObject *parent);
protected:
// turn a vector item into a model row
virtual TaggedUser getItemFromRow(std::vector<QStandardItem *> &row,
const TaggedUser &original) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const TaggedUser &item, std::vector<QStandardItem *> &row) override;
virtual void afterInit() override;
// virtual void customRowSetData(const std::vector<QStandardItem *> &row, int column,
// const QVariant &value, int role) override;
friend class TaggedUsersController;
};
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino
+108 -6
View File
@@ -1,6 +1,7 @@
#include "application.hpp"
#include "singletons/nativemessagingmanager.hpp"
#include "singletons/pathmanager.hpp"
#include "singletons/updatemanager.hpp"
#include "util/networkmanager.hpp"
#include "widgets/lastruncrashdialog.hpp"
@@ -9,6 +10,7 @@
#include <QFile>
#include <QLibrary>
#include <QStringList>
#include <QStyleFactory>
#ifdef USEWINSDK
#include "util/nativeeventhelper.hpp"
@@ -17,7 +19,15 @@
#include <fstream>
#include <iostream>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#endif
int runGui(int argc, char *argv[]);
void runNativeMessagingHost();
void installCustomPalette();
int main(int argc, char *argv[])
{
@@ -30,7 +40,7 @@ int main(int argc, char *argv[])
// TODO: can be any argument
if (args.size() > 0 && args[0].startsWith("chrome-extension://")) {
chatterino::Application::runNativeMessagingHost();
runNativeMessagingHost();
return 0;
}
@@ -47,14 +57,16 @@ int runGui(int argc, char *argv[])
// QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL, true);
QApplication a(argc, argv);
// Install native event handler for hidpi on windows
#ifdef USEWINSDK
a.installNativeEventFilter(new chatterino::util::DpiNativeEventFilter);
#endif
QApplication::setStyle(QStyleFactory::create("Fusion"));
installCustomPalette();
// Initialize NetworkManager
chatterino::util::NetworkManager::init();
// Check for upates
chatterino::singletons::UpdateManager::getInstance().checkForUpdates();
// Initialize application
chatterino::Application::instantiate(argc, argv);
auto app = chatterino::getApp();
@@ -68,7 +80,14 @@ int runGui(int argc, char *argv[])
if (QFile::exists(runningPath)) {
#ifndef DISABLE_CRASH_DIALOG
chatterino::widgets::LastRunCrashDialog dialog;
dialog.exec();
switch (dialog.exec()) {
case QDialog::Accepted: {
}; break;
default: {
_exit(0);
}
}
#endif
} else {
QFile runningFile(runningPath);
@@ -98,3 +117,86 @@ int runGui(int argc, char *argv[])
_exit(0);
}
void runNativeMessagingHost()
{
auto *nm = new chatterino::singletons::NativeMessagingManager;
#ifdef Q_OS_WIN
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
#endif
#if 0
bool bigEndian = isBigEndian();
#endif
std::atomic<bool> ping(false);
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&ping] {
if (!ping.exchange(false)) {
_exit(0);
}
});
timer.setInterval(11000);
timer.start();
while (true) {
char size_c[4];
std::cin.read(size_c, 4);
if (std::cin.eof()) {
break;
}
uint32_t size = *reinterpret_cast<uint32_t *>(size_c);
#if 0
// To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead
uint32_t size = 0;
if (bigEndian) {
size = size_c[3] | static_cast<uint32_t>(size_c[2]) << 8 |
static_cast<uint32_t>(size_c[1]) << 16 | static_cast<uint32_t>(size_c[0]) << 24;
} else {
size = size_c[0] | static_cast<uint32_t>(size_c[1]) << 8 |
static_cast<uint32_t>(size_c[2]) << 16 | static_cast<uint32_t>(size_c[3]) << 24;
}
#endif
std::unique_ptr<char[]> b(new char[size + 1]);
std::cin.read(b.get(), size);
*(b.get() + size) = '\0';
nm->sendToGuiProcess(QByteArray::fromRawData(b.get(), static_cast<int32_t>(size)));
}
}
void installCustomPalette()
{
// borrowed from
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
QPalette darkPalette = qApp->palette();
darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127));
darkPalette.setColor(QPalette::Base, QColor("#333"));
darkPalette.setColor(QPalette::AlternateBase, QColor("#444"));
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35));
darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127));
darkPalette.setColor(QPalette::BrightText, Qt::red);
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80));
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127));
qApp->setPalette(darkPalette);
}
+6 -4
View File
@@ -147,10 +147,10 @@ void Image::loadImage()
loadedEventQueued = true;
QTimer::singleShot(500, [] {
getApp()->emotes->incGeneration();
getApp()->windows->incGeneration();
auto app = getApp();
app->windows->layoutVisibleChatWidgets();
app->windows->layoutChannelViews();
loadedEventQueued = false;
});
}
@@ -240,7 +240,8 @@ int Image::getWidth() const
int Image::getScaledWidth() const
{
return static_cast<int>(this->getWidth() * this->scale);
return static_cast<int>((float)this->getWidth() * this->scale *
getApp()->settings->emoteScale.getValue());
}
int Image::getHeight() const
@@ -253,7 +254,8 @@ int Image::getHeight() const
int Image::getScaledHeight() const
{
return static_cast<int>(this->getHeight() * this->scale);
return static_cast<int>((float)this->getHeight() * this->scale *
getApp()->settings->emoteScale.getValue());
}
} // namespace messages
+77 -77
View File
@@ -3,6 +3,8 @@
#include "application.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/settingsmanager.hpp"
#include "singletons/windowmanager.hpp"
#include "util/benchmark.hpp"
#include <QApplication>
#include <QDebug>
@@ -20,9 +22,9 @@ namespace chatterino {
namespace messages {
namespace layouts {
MessageLayout::MessageLayout(MessagePtr _message)
: message(_message)
, buffer(nullptr)
MessageLayout::MessageLayout(MessagePtr message)
: message_(message)
, buffer_(nullptr)
{
util::DebugCount::increase("message layout");
}
@@ -34,119 +36,116 @@ MessageLayout::~MessageLayout()
Message *MessageLayout::getMessage()
{
return this->message.get();
return this->message_.get();
}
// Height
int MessageLayout::getHeight() const
{
return container.getHeight();
return container_.getHeight();
}
// Layout
// return true if redraw is required
bool MessageLayout::layout(int width, float scale, MessageElement::Flags flags)
{
// BenchmarkGuard benchmark("MessageLayout::layout()");
auto app = getApp();
bool layoutRequired = false;
// check if width changed
bool widthChanged = width != this->currentLayoutWidth;
bool widthChanged = width != this->currentLayoutWidth_;
layoutRequired |= widthChanged;
this->currentLayoutWidth = width;
this->currentLayoutWidth_ = width;
// check if emotes changed
bool imagesChanged = this->emoteGeneration != app->emotes->getGeneration();
layoutRequired |= imagesChanged;
this->emoteGeneration = app->emotes->getGeneration();
// check if text changed
bool textChanged = this->fontGeneration != app->fonts->getGeneration();
layoutRequired |= textChanged;
this->fontGeneration = app->fonts->getGeneration();
// check if layout state changed
if (this->layoutState_ != app->windows->getGeneration()) {
layoutRequired = true;
this->flags |= RequiresBufferUpdate;
this->layoutState_ = app->windows->getGeneration();
}
// check if work mask changed
bool wordMaskChanged = this->currentWordFlags != flags; // app->settings->getWordTypeMask();
layoutRequired |= wordMaskChanged;
this->currentWordFlags = flags; // app->settings->getWordTypeMask();
layoutRequired |= this->currentWordFlags_ != flags;
this->currentWordFlags_ = flags; // app->settings->getWordTypeMask();
// check if timestamp format changed
bool timestampFormatChanged = this->timestampFormat != app->settings->timestampFormat;
layoutRequired |= timestampFormatChanged;
// check if layout was requested manually
layoutRequired |= bool(this->flags & RequiresLayout);
this->flags &= decltype(RequiresLayout)(~RequiresLayout);
// check if dpi changed
bool scaleChanged = this->scale != scale;
layoutRequired |= scaleChanged;
this->scale = scale;
imagesChanged |= scaleChanged;
textChanged |= scaleChanged;
layoutRequired |= this->scale_ != scale;
this->scale_ = scale;
// update word sizes if needed
if (imagesChanged) {
// this->container.updateImages();
this->flags |= MessageLayout::RequiresBufferUpdate;
}
if (textChanged) {
// this->container.updateText();
this->flags |= MessageLayout::RequiresBufferUpdate;
}
if (widthChanged || wordMaskChanged) {
this->deleteBuffer();
}
// return if no layout is required
if (!layoutRequired) {
return false;
}
int oldHeight = this->container_.getHeight();
this->actuallyLayout(width, flags);
if (widthChanged || this->container_.getHeight() != oldHeight) {
this->deleteBuffer();
}
this->invalidateBuffer();
return true;
}
void MessageLayout::actuallyLayout(int width, MessageElement::Flags flags)
void MessageLayout::actuallyLayout(int width, MessageElement::Flags _flags)
{
this->container.begin(width, this->scale, this->message->flags.value);
auto messageFlags = this->message_->flags.value;
for (const std::unique_ptr<MessageElement> &element : this->message->getElements()) {
element->addToContainer(this->container, flags);
if (this->flags & MessageLayout::Expanded ||
(_flags & MessageElement::ModeratorTools &&
!(this->message_->flags & Message::MessageFlags::Disabled))) {
messageFlags = Message::MessageFlags(messageFlags & ~Message::MessageFlags::Collapsed);
}
if (this->height != this->container.getHeight()) {
this->container_.begin(width, this->scale_, messageFlags);
for (const std::unique_ptr<MessageElement> &element : this->message_->getElements()) {
element->addToContainer(this->container_, _flags);
}
if (this->height_ != this->container_.getHeight()) {
this->deleteBuffer();
}
this->container.end();
this->height = this->container.getHeight();
this->container_.end();
this->height_ = this->container_.getHeight();
// collapsed state
this->flags &= ~Flags::Collapsed;
if (this->container_.isCollapsed()) {
this->flags |= Flags::Collapsed;
}
}
// Painting
void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection &selection,
bool isLastReadMessage, bool isWindowFocused)
void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
Selection &selection, bool isLastReadMessage, bool isWindowFocused)
{
auto app = getApp();
QPixmap *pixmap = this->buffer.get();
QPixmap *pixmap = this->buffer_.get();
// create new buffer if required
if (!pixmap) {
#ifdef Q_OS_MACOS
pixmap =
new QPixmap((int)(this->container.getWidth() * painter.device()->devicePixelRatioF()),
(int)(this->container.getHeight() * painter.device()->devicePixelRatioF()));
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
int(container_.getHeight() * painter.device()->devicePixelRatioF()));
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
#else
pixmap = new QPixmap(this->container.getWidth(), std::max(16, this->container.getHeight()));
pixmap = new QPixmap(width, std::max(16, this->container_.getHeight()));
#endif
this->buffer = std::shared_ptr<QPixmap>(pixmap);
this->bufferValid = false;
this->buffer_ = std::shared_ptr<QPixmap>(pixmap);
this->bufferValid_ = false;
util::DebugCount::increase("message drawing buffers");
}
if (!this->bufferValid || !selection.isEmpty()) {
if (!this->bufferValid_ || !selection.isEmpty()) {
this->updateBuffer(pixmap, messageIndex, selection);
}
@@ -155,21 +154,21 @@ void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection
// painter.drawPixmap(0, y, this->container.width, this->container.getHeight(), *pixmap);
// draw gif emotes
this->container.paintAnimatedElements(painter, y);
this->container_.paintAnimatedElements(painter, y);
// draw disabled
if (this->message->flags.HasFlag(Message::Disabled)) {
if (this->message_->flags.HasFlag(Message::Disabled)) {
painter.fillRect(0, y, pixmap->width(), pixmap->height(), app->themes->messages.disabled);
}
// draw selection
if (!selection.isEmpty()) {
this->container.paintSelection(painter, messageIndex, selection, y);
this->container_.paintSelection(painter, messageIndex, selection, y);
}
// draw message seperation line
if (app->settings->seperateMessages.getValue()) {
painter.fillRect(0, y + this->container.getHeight() - 1, this->container.getWidth(), 1,
painter.fillRect(0, y, this->container_.getWidth(), 1,
app->themes->splits.messageSeperator);
}
@@ -180,14 +179,13 @@ void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection
QBrush brush(color, Qt::VerPattern);
painter.fillRect(0, y + this->container.getHeight() - 1, this->container.getWidth(), 1,
brush);
painter.fillRect(0, y + this->container_.getHeight() - 1, pixmap->width(), 1, brush);
}
this->bufferValid = true;
this->bufferValid_ = true;
}
void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &selection)
void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, Selection & /*selection*/)
{
auto app = getApp();
@@ -197,8 +195,10 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s
// draw background
QColor backgroundColor;
if (this->message->flags & Message::Highlighted) {
if (this->message_->flags & Message::Highlighted) {
backgroundColor = app->themes->messages.backgrounds.highlighted;
} else if (this->message_->flags & Message::Subscription) {
backgroundColor = app->themes->messages.backgrounds.subscription;
} else if (app->settings->alternateMessageBackground.getValue() &&
this->flags & MessageLayout::AlternateBackground) {
backgroundColor = app->themes->messages.backgrounds.alternate;
@@ -208,7 +208,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s
painter.fillRect(buffer->rect(), backgroundColor);
// draw message
this->container.paintElements(painter);
this->container_.paintElements(painter);
#ifdef FOURTF
// debug
@@ -226,15 +226,15 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s
void MessageLayout::invalidateBuffer()
{
this->bufferValid = false;
this->bufferValid_ = false;
}
void MessageLayout::deleteBuffer()
{
if (this->buffer != nullptr) {
if (this->buffer_ != nullptr) {
util::DebugCount::decrease("message drawing buffers");
this->buffer = nullptr;
this->buffer_ = nullptr;
}
}
@@ -243,7 +243,7 @@ void MessageLayout::deleteCache()
this->deleteBuffer();
#ifdef XD
this->container.clear();
this->container_.clear();
#endif
}
@@ -256,22 +256,22 @@ void MessageLayout::deleteCache()
const MessageLayoutElement *MessageLayout::getElementAt(QPoint point)
{
// go through all words and return the first one that contains the point.
return this->container.getElementAt(point);
return this->container_.getElementAt(point);
}
int MessageLayout::getLastCharacterIndex() const
{
return this->container.getLastCharacterIndex();
return this->container_.getLastCharacterIndex();
}
int MessageLayout::getSelectionIndex(QPoint position)
{
return this->container.getSelectionIndex(position);
return this->container_.getSelectionIndex(position);
}
void MessageLayout::addSelectionText(QString &str, int from, int to)
{
this->container.addSelectionText(str, from, to);
this->container_.addSelectionText(str, from, to);
}
} // namespace layouts
+18 -18
View File
@@ -22,10 +22,12 @@ public:
enum Flags : uint8_t {
RequiresBufferUpdate = 1 << 1,
RequiresLayout = 1 << 2,
AlternateBackground = 1 << 3
AlternateBackground = 1 << 3,
Collapsed = 1 << 4,
Expanded = 1 << 5,
};
MessageLayout(MessagePtr message);
MessageLayout(MessagePtr message_);
~MessageLayout();
Message *getMessage();
@@ -37,10 +39,10 @@ public:
util::FlagsEnum<Flags> flags;
// Layout
bool layout(int width, float scale, MessageElement::Flags flags);
bool layout(int width, float scale_, MessageElement::Flags flags);
// Painting
void paint(QPainter &painter, int y, int messageIndex, Selection &selection,
void paint(QPainter &painter, int width, int y, int messageIndex, Selection &selection,
bool isLastReadMessage, bool isWindowFocused);
void invalidateBuffer();
void deleteBuffer();
@@ -50,30 +52,28 @@ public:
const MessageLayoutElement *getElementAt(QPoint point);
int getLastCharacterIndex() const;
int getSelectionIndex(QPoint position);
void addSelectionText(QString &str, int from, int to);
void addSelectionText(QString &str, int from = 0, int to = INT_MAX);
// Misc
bool isDisabled() const;
private:
// variables
MessagePtr message;
MessageLayoutContainer container;
std::shared_ptr<QPixmap> buffer = nullptr;
bool bufferValid = false;
MessagePtr message_;
MessageLayoutContainer container_;
std::shared_ptr<QPixmap> buffer_ = nullptr;
bool bufferValid_ = false;
int height = 0;
int height_ = 0;
int currentLayoutWidth = -1;
int fontGeneration = -1;
int emoteGeneration = -1;
QString timestampFormat;
float scale = -1;
unsigned int bufferUpdatedCount = 0;
int currentLayoutWidth_ = -1;
int layoutState_ = -1;
float scale_ = -1;
unsigned int bufferUpdatedCount_ = 0;
MessageElement::Flags currentWordFlags = MessageElement::None;
MessageElement::Flags currentWordFlags_ = MessageElement::None;
int collapsedHeight = 32;
int collapsedHeight_ = 32;
// methods
void actuallyLayout(int width, MessageElement::Flags flags);
@@ -9,6 +9,7 @@
#include <QPainter>
#define COMPACT_EMOTES_OFFSET 6
#define MAX_UNCOLLAPSED_LINES (getApp()->settings->collpseMessagesMinLines.getValue())
namespace chatterino {
namespace messages {
@@ -36,6 +37,12 @@ void MessageLayoutContainer::begin(int _width, float _scale, Message::MessageFla
this->width = _width;
this->scale = _scale;
this->flags = _flags;
auto mediumFontMetrics = getApp()->fonts->getFontMetrics(FontStyle::ChatMedium, _scale);
this->textLineHeight = mediumFontMetrics.height();
this->spaceWidth = mediumFontMetrics.width(' ');
this->dotdotdotWidth = mediumFontMetrics.width("...");
this->_canAddMessages = true;
this->_isCollapsed = false;
}
void MessageLayoutContainer::clear()
@@ -68,12 +75,12 @@ void MessageLayoutContainer::addElementNoLineBreak(MessageLayoutElement *element
bool MessageLayoutContainer::canAddElements()
{
return !(this->flags & Message::MessageFlags::Collapsed && line >= 3);
return this->_canAddMessages;
}
void MessageLayoutContainer::_addElement(MessageLayoutElement *element)
void MessageLayoutContainer::_addElement(MessageLayoutElement *element, bool forceAdd)
{
if (!this->canAddElements()) {
if (!this->canAddElements() && !forceAdd) {
delete element;
return;
}
@@ -129,6 +136,11 @@ void MessageLayoutContainer::breakLine()
yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale;
}
// if (element->getCreator().getFlags() & MessageElement::Badges) {
if (element->getRect().height() < this->textLineHeight) {
yExtra -= (this->textLineHeight - element->getRect().height()) / 2;
}
element->setPosition(QPoint(element->getRect().x() + xOffset + this->margin.left,
element->getRect().y() + this->lineHeight + yExtra));
}
@@ -146,6 +158,12 @@ void MessageLayoutContainer::breakLine()
this->lineStart = this->elements.size();
// this->currentX = (int)(this->scale * 8);
if (this->canCollapse() && line + 1 >= MAX_UNCOLLAPSED_LINES) {
this->_canAddMessages = false;
return;
}
this->currentX = 0;
this->currentY += this->lineHeight;
this->height = this->currentY + (this->margin.bottom * this->scale);
@@ -160,15 +178,32 @@ bool MessageLayoutContainer::atStartOfLine()
bool MessageLayoutContainer::fitsInLine(int _width)
{
return this->currentX + _width <= this->width - this->margin.left - this->margin.right;
return this->currentX + _width <=
(this->width - this->margin.left - this->margin.right -
(this->line + 1 == MAX_UNCOLLAPSED_LINES ? this->dotdotdotWidth : 0));
}
void MessageLayoutContainer::end()
{
if (!this->canAddElements()) {
static TextElement dotdotdot("...", MessageElement::Collapsed, MessageColor::Link);
static QString dotdotdotText("...");
auto *element = new TextLayoutElement(
dotdotdot, dotdotdotText, QSize(this->dotdotdotWidth, this->textLineHeight),
QColor("#00D80A"), FontStyle::ChatMediumBold, this->scale);
// getApp()->themes->messages.textColors.system
this->_addElement(element, true);
this->_isCollapsed = true;
}
if (!this->atStartOfLine()) {
this->breakLine();
}
this->height += this->lineHeight;
if (this->lines.size() != 0) {
this->lines[0].rect.setTop(-100000);
this->lines.back().rect.setBottom(100000);
@@ -177,6 +212,17 @@ void MessageLayoutContainer::end()
}
}
bool MessageLayoutContainer::canCollapse()
{
return getApp()->settings->collpseMessagesMinLines.getValue() > 0 &&
this->flags & Message::MessageFlags::Collapsed;
}
bool MessageLayoutContainer::isCollapsed()
{
return this->_isCollapsed;
}
MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point)
{
for (std::unique_ptr<MessageLayoutElement> &element : this->elements) {
@@ -429,8 +475,6 @@ void MessageLayoutContainer::addSelectionText(QString &str, int from, int to)
for (std::unique_ptr<MessageLayoutElement> &ele : this->elements) {
int c = ele->getSelectionIndexCount();
qDebug() << c;
if (first) {
if (index + c > from) {
ele->addCopyTextToString(str, from - index, to - index);
@@ -76,6 +76,8 @@ struct MessageLayoutContainer {
int getLastCharacterIndex() const;
void addSelectionText(QString &str, int from, int to);
bool isCollapsed();
private:
struct Line {
int startIndex;
@@ -86,7 +88,7 @@ private:
};
// helpers
void _addElement(MessageLayoutElement *element);
void _addElement(MessageLayoutElement *element, bool forceAdd = false);
// variables
float scale = 1.f;
@@ -100,6 +102,12 @@ private:
size_t lineStart = 0;
int lineHeight = 0;
int spaceWidth = 4;
int textLineHeight = 0;
int dotdotdotWidth = 0;
bool _canAddMessages = true;
bool _isCollapsed = false;
bool canCollapse();
std::vector<std::unique_ptr<MessageLayoutElement>> elements;
std::vector<Line> lines;
@@ -188,7 +188,7 @@ int TextLayoutElement::getMouseOverIndex(const QPoint &abs)
auto app = getApp();
QFontMetrics &metrics = app->fonts->getFontMetrics(this->style, this->scale);
QFontMetrics metrics = app->fonts->getFontMetrics(this->style, this->scale);
int x = this->getRect().left();
@@ -209,7 +209,7 @@ int TextLayoutElement::getXFromIndex(int index)
{
auto app = getApp();
QFontMetrics &metrics = app->fonts->getFontMetrics(this->style, this->scale);
QFontMetrics metrics = app->fonts->getFontMetrics(this->style, this->scale);
if (index <= 0) {
return this->getRect().left();
+16 -3
View File
@@ -21,6 +21,8 @@ SBHighlight Message::getScrollBarHighlight() const
{
if (this->flags & Message::Highlighted) {
return SBHighlight(SBHighlight::Highlight);
} else if (this->flags & Message::Subscription) {
return SBHighlight(SBHighlight::Subscription);
}
return SBHighlight();
}
@@ -32,7 +34,19 @@ MessagePtr Message::createSystemMessage(const QString &text)
message->addElement(new TimestampElement(QTime::currentTime()));
message->addElement(new TextElement(text, MessageElement::Text, MessageColor::System));
message->flags.EnableFlag(MessageFlags::System);
message->flags |= MessageFlags::System;
message->flags |= MessageFlags::DoNotTriggerNotification;
message->searchText = text;
return message;
}
MessagePtr Message::createMessage(const QString &text)
{
MessagePtr message(new Message);
message->addElement(new TimestampElement(QTime::currentTime()));
message->addElement(new TextElement(text, MessageElement::Text, MessageColor::Text));
message->searchText = text;
return message;
@@ -63,7 +77,7 @@ MessagePtr Message::createTimeoutMessage(const QString &username, const QString
if (reason.length() > 0) {
text.append(": \"");
text.append(util::ParseTagString(reason));
text.append(util::parseTagString(reason));
text.append("\"");
}
text.append(".");
@@ -89,7 +103,6 @@ MessagePtr Message::createTimeoutMessage(const providers::twitch::BanAction &act
msg->timeoutUser = action.target.name;
msg->count = count;
msg->banAction.reset(new providers::twitch::BanAction(action));
QString text;
+4 -1
View File
@@ -18,6 +18,7 @@ namespace messages {
struct Message {
Message()
: parseTime(QTime::currentTime())
{
util::DebugCount::increase("messages");
}
@@ -39,6 +40,8 @@ struct Message {
Collapsed = (1 << 7),
DisconnectedMessage = (1 << 8),
Untimeout = (1 << 9),
PubSub = (1 << 10),
Subscription = (1 << 11),
};
util::FlagsEnum<MessageFlags> flags;
@@ -50,7 +53,6 @@ struct Message {
QString localizedName;
QString timeoutUser;
std::unique_ptr<providers::twitch::BanAction> banAction;
uint32_t count = 1;
// Messages should not be added after the message is done initializing.
@@ -65,6 +67,7 @@ private:
public:
static std::shared_ptr<Message> createSystemMessage(const QString &text);
static std::shared_ptr<Message> createMessage(const QString &text);
static std::shared_ptr<Message> createTimeoutMessage(const QString &username,
const QString &durationInSeconds,
+7 -16
View File
@@ -70,8 +70,8 @@ ImageElement::ImageElement(Image *_image, MessageElement::Flags flags)
void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
{
if (_flags & this->getFlags()) {
QSize size(this->image->getWidth() * this->image->getScale() * container.getScale(),
this->image->getHeight() * this->image->getScale() * container.getScale());
QSize size(this->image->getScaledWidth() * container.getScale(),
this->image->getScaledHeight() * container.getScale());
container.addElement(
(new ImageLayoutElement(*this, this->image, size))->setLink(this->getLink()));
@@ -97,19 +97,10 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElem
return;
}
int quality = getApp()->settings->preferredEmoteQuality;
Image *_image = this->data.getImage(container.getScale());
Image *_image;
if (quality == 3 && this->data.image3x != nullptr) {
_image = this->data.image3x;
} else if (quality >= 2 && this->data.image2x != nullptr) {
_image = this->data.image2x;
} else {
_image = this->data.image1x;
}
QSize size((int)(container.getScale() * _image->getScaledWidth()),
(int)(container.getScale() * _image->getScaledHeight()));
QSize size(int(container.getScale() * _image->getScaledWidth()),
int(container.getScale() * _image->getScaledHeight()));
container.addElement(
(new ImageLayoutElement(*this, _image, size))->setLink(this->getLink()));
@@ -139,7 +130,7 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
auto app = getApp();
if (_flags & this->getFlags()) {
QFontMetrics &metrics = app->fonts->getFontMetrics(this->style, container.getScale());
QFontMetrics metrics = app->fonts->getFontMetrics(this->style, container.getScale());
for (Word &word : this->words) {
auto getTextLayoutElement = [&](QString text, int width, bool trailingSpace) {
@@ -242,7 +233,7 @@ TextElement *TimestampElement::formatTime(const QTime &time)
QString format = locale.toString(time, getApp()->settings->timestampFormat);
return new TextElement(format, Flags::Timestamp, MessageColor::System, FontStyle::Medium);
return new TextElement(format, Flags::Timestamp, MessageColor::System, FontStyle::ChatMedium);
}
// TWITCH MODERATION
+3 -2
View File
@@ -158,7 +158,7 @@ class TextElement : public MessageElement
public:
TextElement(const QString &text, MessageElement::Flags flags,
const MessageColor &color = MessageColor::Text,
FontStyle style = FontStyle::Medium);
FontStyle style = FontStyle::ChatMedium);
~TextElement() override = default;
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
@@ -169,7 +169,6 @@ public:
// b) which size it wants
class EmoteElement : public MessageElement
{
const util::EmoteData data;
std::unique_ptr<TextElement> textElement;
public:
@@ -177,6 +176,8 @@ public:
~EmoteElement() override = default;
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
const util::EmoteData data;
};
// contains a text, formated depending on the preferences
+1
View File
@@ -7,6 +7,7 @@ struct MessageParseArgs {
bool disablePingSounds = false;
bool isReceivedWhisper = false;
bool isSentWhisper = false;
bool trimSubscriberUsername = false;
};
} // namespace messages
+5
View File
@@ -42,6 +42,11 @@ struct SelectionItem {
{
return this->messageIndex == b.messageIndex && this->charIndex == b.charIndex;
}
bool operator!=(const SelectionItem &b) const
{
return this->operator==(b);
}
};
struct Selection {
+5
View File
@@ -0,0 +1,5 @@
#pragma once
namespace chatterino {
enum class ProviderId { Twitch };
}
+10 -4
View File
@@ -4,6 +4,8 @@
#include "messages/limitedqueuesnapshot.hpp"
#include "messages/message.hpp"
#include <QCoreApplication>
using namespace chatterino::messages;
namespace chatterino {
@@ -13,14 +15,14 @@ namespace irc {
AbstractIrcServer::AbstractIrcServer()
{
// Initialize the connections
this->writeConnection.reset(new Communi::IrcConnection);
this->writeConnection.reset(new IrcConnection);
this->writeConnection->moveToThread(QCoreApplication::instance()->thread());
QObject::connect(this->writeConnection.get(), &Communi::IrcConnection::messageReceived,
[this](auto msg) { this->writeConnectionMessageReceived(msg); });
// Listen to read connection message signals
this->readConnection.reset(new Communi::IrcConnection);
this->readConnection.reset(new IrcConnection);
this->readConnection->moveToThread(QCoreApplication::instance()->thread());
QObject::connect(this->readConnection.get(), &Communi::IrcConnection::messageReceived,
@@ -31,9 +33,13 @@ AbstractIrcServer::AbstractIrcServer()
[this] { this->onConnected(); });
QObject::connect(this->readConnection.get(), &Communi::IrcConnection::disconnected,
[this] { this->onDisconnected(); });
// listen to reconnect request
this->readConnection->reconnectRequested.connect([this] { this->connect(); });
// this->writeConnection->reconnectRequested.connect([this] { this->connect(); });
}
Communi::IrcConnection *AbstractIrcServer::getReadConnection() const
IrcConnection *AbstractIrcServer::getReadConnection() const
{
return this->readConnection.get();
}
@@ -231,7 +237,7 @@ void AbstractIrcServer::addFakeMessage(const QString &data)
{
auto fakeMessage = Communi::IrcMessage::fromData(data.toUtf8(), this->readConnection.get());
this->privateMessageReceived(qobject_cast<Communi::IrcPrivateMessage *>(fakeMessage));
this->messageReceived(fakeMessage);
}
void AbstractIrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
+8 -7
View File
@@ -2,9 +2,9 @@
#include "channel.hpp"
#include <IrcConnection>
#include <IrcMessage>
#include <pajlada/signals/signal.hpp>
#include <providers/irc/ircconnection2.hpp>
#include <functional>
#include <mutex>
@@ -19,7 +19,7 @@ public:
virtual ~AbstractIrcServer() = default;
// connection
Communi::IrcConnection *getReadConnection() const;
IrcConnection *getReadConnection() const;
void connect();
void disconnect();
@@ -33,7 +33,7 @@ public:
// signals
pajlada::Signals::NoArgSignal connected;
pajlada::Signals::NoArgSignal disconnected;
pajlada::Signals::Signal<Communi::IrcPrivateMessage *> onPrivateMessage;
// pajlada::Signals::Signal<Communi::IrcPrivateMessage *> onPrivateMessage;
void addFakeMessage(const QString &data);
@@ -43,8 +43,7 @@ public:
protected:
AbstractIrcServer();
virtual void initializeConnection(Communi::IrcConnection *connection, bool isRead,
bool isWrite) = 0;
virtual void initializeConnection(IrcConnection *connection, bool isRead, bool isWrite) = 0;
virtual std::shared_ptr<Channel> createChannel(const QString &channelName) = 0;
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
@@ -64,10 +63,12 @@ protected:
private:
void initConnection();
std::unique_ptr<Communi::IrcConnection> writeConnection = nullptr;
std::unique_ptr<Communi::IrcConnection> readConnection = nullptr;
std::unique_ptr<IrcConnection> writeConnection = nullptr;
std::unique_ptr<IrcConnection> readConnection = nullptr;
std::mutex connectionMutex;
QTimer pingTimer;
};
} // namespace irc
+36
View File
@@ -0,0 +1,36 @@
#include "ircconnection2.hpp"
namespace chatterino {
namespace providers {
namespace irc {
IrcConnection::IrcConnection(QObject *parent)
: Communi::IrcConnection(parent)
{
this->pingTimer_.setInterval(5000);
this->pingTimer_.start();
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
if (!this->recentlyReceivedMessage_.load()) {
this->sendRaw("PING");
this->reconnectTimer_.start();
}
this->recentlyReceivedMessage_ = false;
});
this->reconnectTimer_.setInterval(5000);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout,
[this] { reconnectRequested.invoke(); });
QObject::connect(this, &Communi::IrcConnection::messageReceived, [this](Communi::IrcMessage *) {
this->recentlyReceivedMessage_ = true;
if (this->reconnectTimer_.isActive()) {
this->reconnectTimer_.stop();
}
});
}
} // namespace irc
} // namespace providers
} // namespace chatterino
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <pajlada/signals/signal.hpp>
#include <IrcConnection>
#include <QTimer>
namespace chatterino {
namespace providers {
namespace irc {
class IrcConnection : public Communi::IrcConnection
{
public:
IrcConnection(QObject *parent = nullptr);
pajlada::Signals::NoArgSignal reconnectRequested;
private:
QTimer pingTimer_;
QTimer reconnectTimer_;
std::atomic<bool> recentlyReceivedMessage_{true};
};
} // namespace irc
} // namespace providers
} // namespace chatterino
+187 -89
View File
@@ -1,6 +1,7 @@
#include "ircmessagehandler.hpp"
#include "application.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "debug/log.hpp"
#include "messages/limitedqueue.hpp"
#include "messages/message.hpp"
@@ -10,6 +11,9 @@
#include "providers/twitch/twitchserver.hpp"
#include "singletons/resourcemanager.hpp"
#include "singletons/windowmanager.hpp"
#include "util/irchelpers.hpp"
#include <IrcMessage>
using namespace chatterino::singletons;
using namespace chatterino::messages;
@@ -24,116 +28,145 @@ IrcMessageHandler &IrcMessageHandler::getInstance()
return instance;
}
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server)
{
this->addMessage(message, message->target(), message->content(), server, false);
}
void IrcMessageHandler::addMessage(Communi::IrcMessage *message, const QString &target,
const QString &content, TwitchServer &server, bool isSub)
{
QString channelName;
if (!trimChannelName(target, channelName)) {
return;
}
auto chan = server.getChannelOrEmpty(channelName);
if (chan->isEmpty()) {
return;
}
messages::MessageParseArgs args;
if (isSub) {
args.trimSubscriberUsername = true;
}
TwitchMessageBuilder builder(chan.get(), message, content, args);
if (isSub || !builder.isIgnored()) {
messages::MessagePtr msg = builder.build();
if (isSub) {
msg->flags |= messages::Message::Subscription;
msg->flags &= ~messages::Message::Highlighted;
} else {
if (msg->flags & messages::Message::Highlighted) {
server.mentionsChannel->addMessage(msg);
getApp()->highlights->addHighlight(msg);
}
}
chan->addMessage(msg);
}
}
void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
{
const auto &tags = message->tags();
auto iterator = tags.find("room-id");
auto app = getApp();
if (iterator != tags.end()) {
auto roomID = iterator.value().toString();
// get twitch channel
QString chanName;
if (!trimChannelName(message->parameter(0), chanName)) {
return;
}
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
TwitchChannel *twitchChannel = dynamic_cast<twitch::TwitchChannel *>(chan.get());
QStringList words = QString(message->toData()).split("#");
if (twitchChannel) {
// room-id
decltype(tags.find("xD")) it;
// ensure the format is valid
if (words.length() < 2) {
return;
}
if ((it = tags.find("room-id")) != tags.end()) {
auto roomID = it.value().toString();
auto app = getApp();
QString channelName = words.at(1);
auto channel = app->twitch.server->getChannelOrEmpty(channelName);
if (channel->isEmpty()) {
return;
}
if (auto twitchChannel = dynamic_cast<twitch::TwitchChannel *>(channel.get())) {
// set the room id of the channel
twitchChannel->setRoomID(roomID);
app->resources->loadChannelData(roomID);
}
app->resources->loadChannelData(roomID);
// Room modes
TwitchChannel::RoomModes roomModes = twitchChannel->getRoomModes();
if ((it = tags.find("emote-only")) != tags.end()) {
roomModes.emoteOnly = it.value() == "1";
}
if ((it = tags.find("subs-only")) != tags.end()) {
roomModes.submode = it.value() == "1";
}
if ((it = tags.find("slow")) != tags.end()) {
roomModes.slowMode = it.value().toInt();
}
if ((it = tags.find("r9k")) != tags.end()) {
roomModes.r9k = it.value() == "1";
}
if ((it = tags.find("broadcaster-lang")) != tags.end()) {
roomModes.broadcasterLang = it.value().toString();
}
twitchChannel->setRoomModes(roomModes);
}
}
void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
{
return;
// // check parameter count
// if (message->parameters().length() < 1) {
// return;
// }
// check parameter count
if (message->parameters().length() < 1) {
return;
}
// QString chanName;
// if (!TrimChannelName(message->parameter(0), chanName)) {
// return;
// }
QString chanName;
if (!trimChannelName(message->parameter(0), chanName)) {
return;
}
// auto app = getApp();
auto app = getApp();
// // get channel
// auto chan = app->twitch.server->getChannelOrEmpty(chanName);
// get channel
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
// if (chan->isEmpty()) {
// debug::Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not found",
// chanName);
// return;
// }
if (chan->isEmpty()) {
debug::Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not found",
chanName);
return;
}
// // check if the chat has been cleared by a moderator
// if (message->parameters().length() == 1) {
// chan->addMessage(Message::createSystemMessage("Chat has been cleared by a
// moderator."));
// check if the chat has been cleared by a moderator
if (message->parameters().length() == 1) {
chan->addMessage(Message::createSystemMessage("Chat has been cleared by a moderator."));
// return;
// }
return;
}
// // get username, duration and message of the timed out user
// QString username = message->parameter(1);
// QString durationInSeconds, reason;
// QVariant v = message->tag("ban-duration");
// if (v.isValid()) {
// durationInSeconds = v.toString();
// }
// get username, duration and message of the timed out user
QString username = message->parameter(1);
QString durationInSeconds, reason;
QVariant v = message->tag("ban-duration");
if (v.isValid()) {
durationInSeconds = v.toString();
}
// v = message->tag("ban-reason");
// if (v.isValid()) {
// reason = v.toString();
// }
v = message->tag("ban-reason");
if (v.isValid()) {
reason = v.toString();
}
// // add the notice that the user has been timed out
// LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
// bool addMessage = true;
// int snapshotLength = snapshot.getLength();
auto timeoutMsg = Message::createTimeoutMessage(username, durationInSeconds, reason, false);
chan->addOrReplaceTimeout(timeoutMsg);
// for (int i = std::max(0, snapshotLength - 20); i < snapshotLength; i++) {
// auto &s = snapshot[i];
// if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == username) {
// MessagePtr replacement(
// Message::createTimeoutMessage(username, durationInSeconds, reason, true));
// chan->replaceMessage(s, replacement);
// addMessage = false;
// break;
// }
// }
// if (addMessage) {
// chan->addMessage(Message::createTimeoutMessage(username, durationInSeconds, reason,
// false));
// }
// // disable the messages from the user
// for (int i = 0; i < snapshotLength; i++) {
// auto &s = snapshot[i];
// if (!(s->flags & Message::Timeout) && s->loginName == username) {
// s->flags.EnableFlag(Message::Disabled);
// }
// }
// // refresh all
// app->windows->repaintVisibleChatWidgets(chan.get());
// refresh all
app->windows->repaintVisibleChatWidgets(chan.get());
}
void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
@@ -144,7 +177,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
auto app = getApp();
QString channelName;
if (!TrimChannelName(message->parameter(0), channelName)) {
if (!trimChannelName(message->parameter(0), channelName)) {
return;
}
@@ -180,6 +213,8 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
app->twitch.server->mentionsChannel->addMessage(_message);
}
app->twitch.server->lastUserThatWhisperedMe.set(builder.userName);
c->addMessage(_message);
if (app->settings->inlineWhispers) {
@@ -190,9 +225,51 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
}
}
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message)
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, TwitchServer &server)
{
// do nothing
auto data = message->toData();
auto tags = message->tags();
auto parameters = message->parameters();
auto target = parameters[0];
QString msgType = tags.value("msg-id", "").toString();
QString content;
if (parameters.size() >= 2) {
content = parameters[1];
}
if (msgType == "sub" || msgType == "resub" || msgType == "subgift") {
// Sub-specific message. I think it's only allowed for "resub" messages atm
if (!content.isEmpty()) {
this->addMessage(message, target, content, server, true);
}
}
auto it = tags.find("system-msg");
if (it != tags.end()) {
auto newMessage =
messages::Message::createSystemMessage(util::parseTagString(it.value().toString()));
newMessage->flags |= messages::Message::Subscription;
QString channelName;
if (message->parameters().size() < 1) {
return;
}
if (!trimChannelName(message->parameter(0), channelName)) {
return;
}
auto chan = server.getChannelOrEmpty(channelName);
if (!chan->isEmpty()) {
chan->addMessage(newMessage);
}
}
}
void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message)
@@ -231,7 +308,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
// auto channel = app->twitch.server->getChannelOrEmpty(channelName);
// if (channel->isEmpty()) {
// debug::Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel manager",
// debug::Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel
// manager",
// channelName);
// return;
// }
@@ -257,6 +335,26 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMes
this->handleNoticeMessage(message);
}
void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
{
auto app = getApp();
auto channel = app->twitch.server->getChannelOrEmpty(message->parameter(0).remove(0, 1));
if (TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
twitchChannel->addJoinedUser(message->nick());
}
}
void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
{
auto app = getApp();
auto channel = app->twitch.server->getChannelOrEmpty(message->parameter(0).remove(0, 1));
if (TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
twitchChannel->addPartedUser(message->nick());
}
}
} // namespace twitch
} // namespace providers
} // namespace chatterino
+12 -1
View File
@@ -6,6 +6,8 @@ namespace chatterino {
namespace providers {
namespace twitch {
class TwitchServer;
class IrcMessageHandler
{
IrcMessageHandler() = default;
@@ -13,14 +15,23 @@ class IrcMessageHandler
public:
static IrcMessageHandler &getInstance();
void handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server);
void handleRoomStateMessage(Communi::IrcMessage *message);
void handleClearChatMessage(Communi::IrcMessage *message);
void handleUserStateMessage(Communi::IrcMessage *message);
void handleWhisperMessage(Communi::IrcMessage *message);
void handleUserNoticeMessage(Communi::IrcMessage *message);
void handleUserNoticeMessage(Communi::IrcMessage *message, TwitchServer &server);
void handleModeMessage(Communi::IrcMessage *message);
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message);
void handleJoinMessage(Communi::IrcMessage *message);
void handlePartMessage(Communi::IrcMessage *message);
private:
void addMessage(Communi::IrcMessage *message, const QString &target, const QString &content,
TwitchServer &server, bool isResub);
};
} // namespace twitch
-1
View File
@@ -3,7 +3,6 @@
#include "debug/log.hpp"
#include "providers/twitch/pubsubactions.hpp"
#include "providers/twitch/pubsubhelpers.hpp"
#include "singletons/accountmanager.hpp"
#include "util/rapidjson-helpers.hpp"
#include <rapidjson/error/en.h>
-1
View File
@@ -1,7 +1,6 @@
#include "providers/twitch/pubsubhelpers.hpp"
#include "providers/twitch/pubsubactions.hpp"
#include "singletons/accountmanager.hpp"
#include "util/rapidjson-helpers.hpp"
namespace chatterino {
+1 -1
View File
@@ -12,7 +12,7 @@ namespace twitch {
TwitchAccount::TwitchAccount(const QString &_username, const QString &_oauthToken,
const QString &_oauthClient, const QString &_userID)
: controllers::accounts::Account("Twitch")
: controllers::accounts::Account(ProviderId::Twitch)
, oauthClient(_oauthClient)
, oauthToken(_oauthToken)
, userName(_username)
-1
View File
@@ -55,7 +55,6 @@ public:
bool isAnon() const;
void loadIgnores();
void ignore(const QString &targetName,
std::function<void(IgnoreResult, const QString &)> onFinished);
void ignoreByID(const QString &targetUserID, const QString &targetName,
+14 -20
View File
@@ -15,6 +15,9 @@ TwitchAccountManager::TwitchAccountManager()
auto currentUser = this->getCurrent();
currentUser->loadIgnores();
});
this->accounts.itemRemoved.connect(
[this](const auto &acc) { this->removeUser(acc.item.get()); });
}
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
@@ -32,7 +35,7 @@ std::vector<QString> TwitchAccountManager::getUsernames() const
std::lock_guard<std::mutex> lock(this->mutex);
for (const auto &user : this->users) {
for (const auto &user : this->accounts.getVector()) {
userNames.push_back(user->getUserName());
}
@@ -44,7 +47,7 @@ std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
{
std::lock_guard<std::mutex> lock(this->mutex);
for (const auto &user : this->users) {
for (const auto &user : this->accounts.getVector()) {
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0) {
return user;
}
@@ -134,25 +137,16 @@ void TwitchAccountManager::load()
});
}
bool TwitchAccountManager::removeUser(const QString &username)
bool TwitchAccountManager::removeUser(TwitchAccount *account)
{
if (!this->userExists(username)) {
return false;
const auto &accs = this->accounts.getVector();
std::string userID(account->getUserId().toStdString());
if (!userID.empty()) {
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" + userID);
}
this->mutex.lock();
this->users.erase(std::remove_if(this->users.begin(), this->users.end(), [username](auto user) {
if (user->getUserName() == username) {
std::string userID(user->getUserId().toStdString());
assert(!userID.empty());
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" + userID);
return true;
}
return false;
}));
this->mutex.unlock();
if (username == qS(this->currentUsername.getValue())) {
if (account->getUserName() == qS(this->currentUsername.getValue())) {
// The user that was removed is the current user, log into the anonymous user
this->currentUsername = "";
}
@@ -187,9 +181,9 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
auto newUser = std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
userData.clientID, userData.userID);
std::lock_guard<std::mutex> lock(this->mutex);
// std::lock_guard<std::mutex> lock(this->mutex);
this->users.push_back(newUser);
this->accounts.insertItem(newUser);
return AddUserResponse::UserAdded;
}
@@ -15,10 +15,11 @@
//
namespace chatterino {
namespace singletons {
class AccountManager;
} // namespace singletons
namespace controllers {
namespace accounts {
class AccountController;
}
} // namespace controllers
namespace providers {
namespace twitch {
@@ -46,8 +47,6 @@ public:
void reloadUsers();
void load();
bool removeUser(const QString &username);
pajlada::Settings::Setting<std::string> currentUsername = {"/accounts/current", ""};
pajlada::Signals::NoArgSignal currentUserChanged;
pajlada::Signals::NoArgSignal userListUpdated;
@@ -63,14 +62,14 @@ private:
UserAdded,
};
AddUserResponse addUser(const UserData &data);
bool removeUser(TwitchAccount *account);
std::shared_ptr<TwitchAccount> currentUser;
std::shared_ptr<TwitchAccount> anonymousUser;
std::vector<std::shared_ptr<TwitchAccount>> users;
mutable std::mutex mutex;
friend class chatterino::singletons::AccountManager;
friend class chatterino::controllers::accounts::AccountController;
};
} // namespace twitch
+102 -6
View File
@@ -5,7 +5,6 @@
#include "messages/message.hpp"
#include "providers/twitch/pubsub.hpp"
#include "providers/twitch/twitchmessagebuilder.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/ircmanager.hpp"
#include "singletons/settingsmanager.hpp"
@@ -45,7 +44,8 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
this->refreshLiveStatus(); //
});
this->managedConnect(app->accounts->Twitch.currentUserChanged, [this]() { this->setMod(false); });
this->managedConnect(app->accounts->twitch.currentUserChanged,
[this]() { this->setMod(false); });
auto refreshPubSubState = [=]() {
if (!this->hasModRights()) {
@@ -56,7 +56,7 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
return;
}
auto account = app->accounts->Twitch.getCurrent();
auto account = app->accounts->twitch.getCurrent();
if (account && !account->getUserId().isEmpty()) {
app->twitch.pubsub->listenToChannelModerationActions(this->roomID, account);
}
@@ -64,7 +64,7 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
this->userStateChanged.connect(refreshPubSubState);
this->roomIDchanged.connect(refreshPubSubState);
this->managedConnect(app->accounts->Twitch.currentUserChanged, refreshPubSubState);
this->managedConnect(app->accounts->twitch.currentUserChanged, refreshPubSubState);
refreshPubSubState();
this->fetchMessages.connect([this] {
@@ -85,7 +85,7 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
};
auto doRefreshChatters = [=]() {
const auto streamStatus = this->GetStreamStatus();
const auto streamStatus = this->getStreamStatus();
if (app->settings->onlyFetchChattersForSmallerStreamers) {
if (streamStatus.live && streamStatus.viewerCount > app->settings->smallStreamerLimit) {
@@ -102,6 +102,10 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
this->chattersListTimer = new QTimer;
QObject::connect(this->chattersListTimer, &QTimer::timeout, doRefreshChatters);
this->chattersListTimer->start(5 * 60 * 1000);
// for (int i = 0; i < 1000; i++) {
// this->addMessage(messages::Message::createSystemMessage("asdf"));
// }
}
TwitchChannel::~TwitchChannel()
@@ -186,7 +190,7 @@ bool TwitchChannel::isBroadcaster()
{
auto app = getApp();
return this->name == app->accounts->Twitch.getCurrent()->getUserName();
return this->name == app->accounts->twitch.getCurrent()->getUserName();
}
bool TwitchChannel::hasModRights()
@@ -206,6 +210,93 @@ void TwitchChannel::addRecentChatter(const std::shared_ptr<messages::Message> &m
this->completionModel.addUser(message->displayName);
}
void TwitchChannel::addJoinedUser(const QString &user)
{
auto *app = getApp();
if (user == app->accounts->twitch.getCurrent()->getUserName() ||
!app->settings->showJoins.getValue()) {
return;
}
std::lock_guard<std::mutex> guard(this->joinedUserMutex);
joinedUsers << user;
if (!this->joinedUsersMergeQueued) {
this->joinedUsersMergeQueued = true;
QTimer::singleShot(500, &this->object, [this] {
std::lock_guard<std::mutex> guard(this->joinedUserMutex);
auto message = messages::Message::createSystemMessage("Users joined: " +
this->joinedUsers.join(", "));
message->flags |= messages::Message::Collapsed;
this->addMessage(message);
this->joinedUsers.clear();
this->joinedUsersMergeQueued = false;
});
}
}
void TwitchChannel::addPartedUser(const QString &user)
{
auto *app = getApp();
if (user == app->accounts->twitch.getCurrent()->getUserName() ||
!app->settings->showJoins.getValue()) {
return;
}
std::lock_guard<std::mutex> guard(this->partedUserMutex);
partedUsers << user;
if (!this->partedUsersMergeQueued) {
this->partedUsersMergeQueued = true;
QTimer::singleShot(500, &this->object, [this] {
std::lock_guard<std::mutex> guard(this->partedUserMutex);
auto message = messages::Message::createSystemMessage("Users parted: " +
this->partedUsers.join(", "));
message->flags |= messages::Message::Collapsed;
this->addMessage(message);
this->partedUsers.clear();
this->partedUsersMergeQueued = false;
});
}
}
TwitchChannel::RoomModes TwitchChannel::getRoomModes()
{
std::lock_guard<std::mutex> lock(this->roomModeMutex);
return this->roomModes;
}
void TwitchChannel::setRoomModes(const RoomModes &_roomModes)
{
{
std::lock_guard<std::mutex> lock(this->roomModeMutex);
this->roomModes = _roomModes;
}
this->roomModesChanged.invoke();
}
bool TwitchChannel::isLive() const
{
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
return this->streamStatus.live;
}
TwitchChannel::StreamStatus TwitchChannel::getStreamStatus() const
{
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
return this->streamStatus;
}
void TwitchChannel::setLive(bool newLiveStatus)
{
bool gotNewLiveStatus = false;
@@ -289,6 +380,11 @@ void TwitchChannel::refreshLiveStatus()
QString::number(diff / 3600) + "h " + QString::number(diff % 3600 / 60) + "m";
channel->streamStatus.rerun = false;
if (stream.HasMember("stream_type")) {
channel->streamStatus.streamType = stream["stream_type"].GetString();
} else {
channel->streamStatus.streamType = QString();
}
if (stream.HasMember("broadcast_platform")) {
const auto &broadcastPlatformValue = stream["broadcast_platform"];
+29 -10
View File
@@ -7,6 +7,7 @@
#include "singletons/emotemanager.hpp"
#include "singletons/ircmanager.hpp"
#include "util/concurrentmap.hpp"
#include "util/mutexvalue.hpp"
#include <pajlada/signals/signalholder.hpp>
@@ -31,6 +32,7 @@ public:
QString title;
QString game;
QString uptime;
QString streamType;
};
struct UserState {
@@ -38,6 +40,15 @@ public:
bool broadcaster;
};
struct RoomModes {
bool submode = false;
bool r9k = false;
bool emoteOnly = false;
// int folowerOnly = 0;
int slowMode = 0;
QString broadcasterLang;
};
~TwitchChannel() final;
void reloadChannelEmotes();
@@ -52,6 +63,8 @@ public:
bool hasModRights();
void addRecentChatter(const std::shared_ptr<messages::Message> &message) final;
void addJoinedUser(const QString &user);
void addPartedUser(const QString &user);
const std::shared_ptr<chatterino::util::EmoteMap> bttvChannelEmotes;
const std::shared_ptr<chatterino::util::EmoteMap> ffzChannelEmotes;
@@ -66,25 +79,21 @@ public:
pajlada::Signals::NoArgBoltSignal fetchMessages;
pajlada::Signals::NoArgSignal userStateChanged;
pajlada::Signals::NoArgSignal roomModesChanged;
QString roomID;
StreamStatus GetStreamStatus() const
{
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
return this->streamStatus;
}
RoomModes getRoomModes();
void setRoomModes(const RoomModes &roomModes);
StreamStatus getStreamStatus() const;
struct NameOptions {
QString displayName;
QString localizedName;
};
bool IsLive() const
{
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
return this->streamStatus.live;
}
bool isLive() const;
private:
explicit TwitchChannel(const QString &channelName, Communi::IrcConnection *readConnection);
@@ -103,6 +112,16 @@ private:
bool mod;
QByteArray messageSuffix;
QString lastSentMessage;
RoomModes roomModes;
std::mutex roomModeMutex;
QObject object;
std::mutex joinedUserMutex;
QStringList joinedUsers;
bool joinedUsersMergeQueued = false;
std::mutex partedUserMutex;
QStringList partedUsers;
bool partedUsersMergeQueued = false;
Communi::IrcConnection *readConnection;
+1 -1
View File
@@ -5,7 +5,7 @@ namespace chatterino {
namespace providers {
namespace twitch {
bool TrimChannelName(const QString &channelName, QString &outChannelName)
bool trimChannelName(const QString &channelName, QString &outChannelName)
{
if (channelName.length() < 3) {
debug::Log("channel name length below 3");
+1 -1
View File
@@ -6,7 +6,7 @@ namespace chatterino {
namespace providers {
namespace twitch {
bool TrimChannelName(const QString &channelName, QString &outChannelName);
bool trimChannelName(const QString &channelName, QString &outChannelName);
} // namespace twitch
} // namespace providers
+41 -26
View File
@@ -1,11 +1,11 @@
#include "providers/twitch/twitchmessagebuilder.hpp"
#include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "controllers/ignores/ignorecontroller.hpp"
#include "debug/log.hpp"
#include "providers/twitch/twitchchannel.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/ircmanager.hpp"
#include "singletons/resourcemanager.hpp"
@@ -68,7 +68,18 @@ bool TwitchMessageBuilder::isIgnored() const
if (app->settings->enableTwitchIgnoredUsers && this->tags.contains("user-id")) {
auto sourceUserID = this->tags.value("user-id").toString();
for (const auto &user : app->accounts->Twitch.getCurrent()->getIgnores()) {
for (const auto &user : app->accounts->twitch.getCurrent()->getIgnores()) {
if (sourceUserID == user.id) {
debug::Log("Blocking message because it's from blocked user {}", user.name);
return true;
}
}
}
if (app->settings->enableTwitchIgnoredUsers && this->tags.contains("user-id")) {
auto sourceUserID = this->tags.value("user-id").toString();
for (const auto &user : app->accounts->twitch.getCurrent()->getIgnores()) {
if (sourceUserID == user.id) {
debug::Log("Blocking message because it's from blocked user {}", user.name);
return true;
@@ -86,12 +97,14 @@ MessagePtr TwitchMessageBuilder::build()
// PARSING
this->parseUsername();
#ifdef XD
if (this->originalMessage.length() > 100) {
this->message->flags |= Message::Collapsed;
this->emplace<EmoteElement>(getApp()->resources->badgeCollapsed, MessageElement::Collapsed);
}
#endif
//#ifdef XD
// if (this->originalMessage.length() > 100) {
// this->message->flags |= Message::Collapsed;
// this->emplace<EmoteElement>(getApp()->resources->badgeCollapsed,
// MessageElement::Collapsed);
// }
//#endif
this->message->flags |= Message::Collapsed;
// PARSING
this->parseMessageID();
@@ -141,15 +154,8 @@ MessagePtr TwitchMessageBuilder::build()
this->appendTwitchEmote(ircMessage, emote, twitchEmotes);
}
struct {
bool operator()(const std::pair<long, util::EmoteData> &lhs,
const std::pair<long, util::EmoteData> &rhs)
{
return lhs.first < rhs.first;
}
} customLess;
std::sort(twitchEmotes.begin(), twitchEmotes.end(), customLess);
std::sort(twitchEmotes.begin(), twitchEmotes.end(),
[](const auto &a, const auto &b) { return a.first < b.first; });
}
auto currentTwitchEmote = twitchEmotes.begin();
@@ -282,10 +288,16 @@ void TwitchMessageBuilder::parseUsername()
// username
this->userName = this->ircMessage->nick();
if (this->userName.isEmpty()) {
if (this->userName.isEmpty() || this->args.trimSubscriberUsername) {
this->userName = this->tags.value(QLatin1String("login")).toString();
}
// display name
// auto displayNameVariant = this->tags.value("display-name");
// if (displayNameVariant.isValid()) {
// this->userName = displayNameVariant.toString() + " (" + this->userName + ")";
// }
this->message->loginName = this->userName;
}
@@ -350,14 +362,14 @@ void TwitchMessageBuilder::appendUsername()
} else if (this->args.isReceivedWhisper) {
// Sender username
this->emplace<TextElement>(usernameText, MessageElement::Text, this->usernameColor,
FontStyle::MediumBold)
FontStyle::ChatMediumBold)
->setLink({Link::UserInfo, this->userName});
auto currentUser = app->accounts->Twitch.getCurrent();
auto currentUser = app->accounts->twitch.getCurrent();
// Separator
this->emplace<TextElement>("->", MessageElement::Text,
app->themes->messages.textColors.system, FontStyle::Medium);
app->themes->messages.textColors.system, FontStyle::ChatMedium);
QColor selfColor = currentUser->color;
if (!selfColor.isValid()) {
@@ -366,14 +378,14 @@ void TwitchMessageBuilder::appendUsername()
// Your own username
this->emplace<TextElement>(currentUser->getUserName() + ":", MessageElement::Text,
selfColor, FontStyle::MediumBold);
selfColor, FontStyle::ChatMediumBold);
} else {
if (!this->action) {
usernameText += ":";
}
this->emplace<TextElement>(usernameText, MessageElement::Text, this->usernameColor,
FontStyle::MediumBold)
FontStyle::ChatMediumBold)
->setLink({Link::UserInfo, this->userName});
}
}
@@ -385,7 +397,7 @@ void TwitchMessageBuilder::parseHighlights()
auto app = getApp();
auto currentUser = app->accounts->Twitch.getCurrent();
auto currentUser = app->accounts->twitch.getCurrent();
QString currentUsername = currentUser->getUserName();
@@ -571,11 +583,13 @@ void TwitchMessageBuilder::appendTwitchBadges()
QString cheerAmountQS = badge.mid(5);
std::string versionKey = cheerAmountQS.toStdString();
QString tooltip = QString("Twitch cheer ") + cheerAmountQS;
// Try to fetch channel-specific bit badge
try {
const auto &badge = channelResources.badgeSets.at("bits").versions.at(versionKey);
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity);
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity)
->setTooltip(tooltip);
continue;
} catch (const std::out_of_range &) {
// Channel does not contain a special bit badge for this version
@@ -584,7 +598,8 @@ void TwitchMessageBuilder::appendTwitchBadges()
// Use default bit badge
try {
const auto &badge = app->resources->badgeSets.at("bits").versions.at(versionKey);
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity);
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity)
->setTooltip(tooltip);
} catch (const std::out_of_range &) {
debug::Log("No default bit badge for version {} found", versionKey);
continue;
+33 -46
View File
@@ -1,16 +1,18 @@
#include "twitchserver.hpp"
#include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "providers/twitch/ircmessagehandler.hpp"
#include "providers/twitch/twitchaccount.hpp"
#include "providers/twitch/twitchhelpers.hpp"
#include "providers/twitch/twitchmessagebuilder.hpp"
#include "singletons/accountmanager.hpp"
#include "util/posttothread.hpp"
#include <IrcCommand>
#include <cassert>
using namespace Communi;
// using namespace Communi;
using namespace chatterino::singletons;
namespace chatterino {
@@ -27,13 +29,14 @@ TwitchServer::TwitchServer()
void TwitchServer::initialize()
{
getApp()->accounts->Twitch.currentUserChanged.connect(
getApp()->accounts->twitch.currentUserChanged.connect(
[this]() { util::postToThread([this] { this->connect(); }); });
}
void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead, bool isWrite)
void TwitchServer::initializeConnection(providers::irc::IrcConnection *connection, bool isRead,
bool isWrite)
{
std::shared_ptr<TwitchAccount> account = getApp()->accounts->Twitch.getCurrent();
std::shared_ptr<TwitchAccount> account = getApp()->accounts->twitch.getCurrent();
qDebug() << "logging in as" << account->getUserName();
@@ -56,9 +59,9 @@ void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
// this->refreshIgnoredUsers(username, oauthClient, oauthToken);
}
connection->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/membership"));
connection->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/commands"));
connection->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/tags"));
connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/membership"));
connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/commands"));
connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/tags"));
connection->setHost("irc.chat.twitch.tv");
connection->setPort(6667);
@@ -74,69 +77,53 @@ std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName)
return std::shared_ptr<Channel>(channel);
}
void TwitchServer::privateMessageReceived(IrcPrivateMessage *message)
void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
{
QString channelName;
if (!TrimChannelName(message->target(), channelName)) {
return;
}
this->onPrivateMessage.invoke(message);
auto chan = this->getChannelOrEmpty(channelName);
if (chan->isEmpty()) {
return;
}
messages::MessageParseArgs args;
TwitchMessageBuilder builder(chan.get(), message, args);
if (!builder.isIgnored()) {
messages::MessagePtr _message = builder.build();
if (_message->flags & messages::Message::Highlighted) {
this->mentionsChannel->addMessage(_message);
}
chan->addMessage(_message);
}
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
}
void TwitchServer::messageReceived(IrcMessage *message)
void TwitchServer::messageReceived(Communi::IrcMessage *message)
{
// this->readConnection
if (message->type() == IrcMessage::Type::Private) {
if (message->type() == Communi::IrcMessage::Type::Private) {
// We already have a handler for private messages
return;
}
const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance();
if (command == "ROOMSTATE") {
IrcMessageHandler::getInstance().handleRoomStateMessage(message);
handler.handleRoomStateMessage(message);
} else if (command == "CLEARCHAT") {
IrcMessageHandler::getInstance().handleClearChatMessage(message);
handler.handleClearChatMessage(message);
} else if (command == "USERSTATE") {
IrcMessageHandler::getInstance().handleUserStateMessage(message);
handler.handleUserStateMessage(message);
} else if (command == "WHISPER") {
IrcMessageHandler::getInstance().handleWhisperMessage(message);
handler.handleWhisperMessage(message);
} else if (command == "USERNOTICE") {
IrcMessageHandler::getInstance().handleUserNoticeMessage(message);
handler.handleUserNoticeMessage(message, *this);
} else if (command == "MODE") {
IrcMessageHandler::getInstance().handleModeMessage(message);
handler.handleModeMessage(message);
} else if (command == "NOTICE") {
IrcMessageHandler::getInstance().handleNoticeMessage(
static_cast<IrcNoticeMessage *>(message));
handler.handleNoticeMessage(static_cast<Communi::IrcNoticeMessage *>(message));
} else if (command == "JOIN") {
handler.handleJoinMessage(message);
} else if (command == "PART") {
handler.handlePartMessage(message);
}
}
void TwitchServer::writeConnectionMessageReceived(IrcMessage *message)
void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
{
switch (message->type()) {
case IrcMessage::Type::Notice: {
case Communi::IrcMessage::Type::Notice: {
IrcMessageHandler::getInstance().handleWriteConnectionNoticeMessage(
static_cast<IrcNoticeMessage *>(message));
static_cast<Communi::IrcNoticeMessage *>(message));
} break;
default:;
}
}
+11 -1
View File
@@ -3,6 +3,7 @@
#include "providers/irc/abstractircserver.hpp"
#include "providers/twitch/twitchaccount.hpp"
#include "providers/twitch/twitchchannel.hpp"
#include "util/mutexvalue.hpp"
#include <memory>
@@ -22,12 +23,17 @@ public:
std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID);
util::MutexValue<QString> lastUserThatWhisperedMe;
// QString getLastWhisperedPerson() const;
// void setLastWhisperedPerson(const QString &person);
const ChannelPtr whispersChannel;
const ChannelPtr mentionsChannel;
IndirectChannel watchingChannel;
protected:
void initializeConnection(Communi::IrcConnection *connection, bool isRead,
void initializeConnection(providers::irc::IrcConnection *connection, bool isRead,
bool isWrite) override;
std::shared_ptr<Channel> createChannel(const QString &channelName) override;
@@ -38,6 +44,10 @@ protected:
std::shared_ptr<Channel> getCustomChannel(const QString &channelname) override;
QString cleanChannelName(const QString &dirtyChannelName) override;
private:
// mutable std::mutex lastWhisperedPersonMutex;
// QString lastWhisperedPerson;
};
} // namespace twitch
-12
View File
@@ -1,12 +0,0 @@
#include "singletons/accountmanager.hpp"
namespace chatterino {
namespace singletons {
void AccountManager::load()
{
this->Twitch.load();
}
} // namespace singletons
} // namespace chatterino
-21
View File
@@ -1,21 +0,0 @@
#pragma once
#include "providers/twitch/twitchaccountmanager.hpp"
namespace chatterino {
namespace singletons {
class AccountManager
{
public:
AccountManager() = default;
~AccountManager() = delete;
void load();
providers::twitch::TwitchAccountManager Twitch;
};
} // namespace singletons
} // namespace chatterino
+41 -10
View File
@@ -86,8 +86,8 @@ EmoteManager::EmoteManager()
void EmoteManager::initialize()
{
getApp()->accounts->Twitch.currentUserChanged.connect([this] {
auto currentUser = getApp()->accounts->Twitch.getCurrent();
getApp()->accounts->twitch.currentUserChanged.connect([this] {
auto currentUser = getApp()->accounts->twitch.getCurrent();
assert(currentUser);
this->refreshTwitchEmotes(currentUser);
});
@@ -130,13 +130,23 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName,
QString code = emoteObject.value("code").toString();
// emoteObject.value("imageType").toString();
QString link = linkTemplate;
link.detach();
auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [&] {
util::EmoteData emoteData;
QString link = linkTemplate;
link.detach();
emoteData.image1x = new Image(link.replace("{{id}}", id).replace("{{image}}", "1x"),
1, code, code + "<br />Channel BTTV Emote");
link = linkTemplate;
link.detach();
emoteData.image2x = new Image(link.replace("{{id}}", id).replace("{{image}}", "2x"),
0.5, code, code + "<br />Channel BTTV Emote");
link = linkTemplate;
link.detach();
emoteData.image3x = new Image(link.replace("{{id}}", id).replace("{{image}}", "3x"),
0.25, code, code + "<br />Channel BTTV Emote");
emoteData.pageLink = "https://manage.betterttv.net/emotes/" + id;
link = link.replace("{{id}}", id).replace("{{image}}", "1x");
auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [&code, &link] {
return util::EmoteData(new Image(link, 1, code, code + "<br/>Channel BTTV Emote"));
return emoteData;
});
this->bttvChannelEmotes.insert(code, emote);
@@ -182,9 +192,11 @@ void EmoteManager::reloadFFZChannelEmotes(const QString &channelName,
QJsonObject urls = emoteObject.value("urls").toObject();
auto emote = this->getFFZChannelEmoteFromCaches().getOrAdd(id, [&code, &urls] {
auto emote = this->getFFZChannelEmoteFromCaches().getOrAdd(id, [id, &code, &urls] {
util::EmoteData emoteData;
FillInFFZEmoteData(urls, code, code + "<br/>Channel FFZ Emote", emoteData);
emoteData.pageLink =
QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
return emoteData;
});
@@ -436,6 +448,7 @@ void EmoteManager::refreshTwitchEmotes(const std::shared_ptr<TwitchAccount> &use
[=, &emoteData](const QJsonObject &root) {
emoteData.emoteSets.clear();
emoteData.emoteCodes.clear();
auto emoticonSets = root.value("emoticon_sets").toObject();
for (QJsonObject::iterator it = emoticonSets.begin(); it != emoticonSets.end(); ++it) {
std::string emoteSetString = it.key().toStdString();
@@ -443,7 +456,7 @@ void EmoteManager::refreshTwitchEmotes(const std::shared_ptr<TwitchAccount> &use
for (QJsonValue emoteValue : emoteSetList) {
QJsonObject emoticon = emoteValue.toObject();
std::string id = emoticon["id"].toString().toStdString();
std::string id = QString::number(emoticon["id"].toInt()).toStdString();
std::string code = emoticon["code"].toString().toStdString();
emoteData.emoteSets[emoteSetString].push_back({id, code});
emoteData.emoteCodes.push_back(code);
@@ -483,6 +496,7 @@ void EmoteManager::loadBTTVEmotes()
code + "<br />Global BTTV Emote");
emoteData.image3x = new Image(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25, code,
code + "<br />Global BTTV Emote");
emoteData.pageLink = "https://manage.betterttv.net/emotes/" + id;
this->bttvGlobalEmotes.insert(code, emoteData);
codes.push_back(code.toStdString());
@@ -510,10 +524,13 @@ void EmoteManager::loadFFZEmotes()
QJsonObject object = emote.toObject();
QString code = object.value("name").toString();
int id = object.value("id").toInt();
QJsonObject urls = object.value("urls").toObject();
util::EmoteData emoteData;
FillInFFZEmoteData(urls, code, code + "<br/>Global FFZ Emote", emoteData);
emoteData.pageLink =
QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
this->ffzGlobalEmotes.insert(code, emoteData);
codes.push_back(code.toStdString());
@@ -530,6 +547,20 @@ util::EmoteData EmoteManager::getTwitchEmoteById(long id, const QString &emoteNa
{
QString _emoteName = emoteName;
_emoteName.replace("<", "&lt;");
_emoteName.replace(">", "&gt;");
static QMap<QString, QString> emoteNameReplacements{
{"[oO](_|\\.)[oO]", "O_o"}, {"\\&gt\\;\\(", "&gt;("}, {"\\&lt\\;3", "&lt;3"},
{"\\:-?(o|O)", ":O"}, {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, {"\\:-?\\)", ":)"},
{"\\:-?D", ":D"}, {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
{"R-?\\)", "R)"}, {"B-?\\)", "B)"},
};
auto it = emoteNameReplacements.find(_emoteName);
if (it != emoteNameReplacements.end()) {
_emoteName = it.value();
}
return _twitchEmoteFromCache.getOrAdd(id, [&emoteName, &_emoteName, &id] {
util::EmoteData newEmoteData;
-12
View File
@@ -46,16 +46,6 @@ public:
util::EmoteData getTwitchEmoteById(long int id, const QString &emoteName);
int getGeneration()
{
return _generation;
}
void incGeneration()
{
_generation++;
}
pajlada::Signals::NoArgSignal &getGifUpdateSignal();
// Bit badge/emotes?
@@ -145,8 +135,6 @@ private:
pajlada::Signals::NoArgSignal gifUpdateTimerSignal;
QTimer gifUpdateTimer;
bool gifUpdateTimerInitiated = false;
int _generation = 0;
};
} // namespace singletons
+103 -74
View File
@@ -3,6 +3,10 @@
#include <QDebug>
#include <QtGlobal>
#include "application.hpp"
#include "util/assertinguithread.hpp"
#include "windowmanager.hpp"
#ifdef Q_OS_WIN32
#define DEFAULT_FONT_FAMILY "Segoe UI"
#define DEFAULT_FONT_SIZE 10
@@ -20,86 +24,111 @@ namespace chatterino {
namespace singletons {
FontManager::FontManager()
: currentFontFamily("/appearance/currentFontFamily", DEFAULT_FONT_FAMILY)
, currentFontSize("/appearance/currentFontSize", DEFAULT_FONT_SIZE)
// , currentFont(this->currentFontFamily.getValue().c_str(), currentFontSize.getValue())
: chatFontFamily("/appearance/currentFontFamily", DEFAULT_FONT_FAMILY)
, chatFontSize("/appearance/currentFontSize", DEFAULT_FONT_SIZE)
{
qDebug() << "init FontManager";
this->currentFontFamily.connect([this](const std::string &newValue, auto) {
this->incGeneration();
// this->currentFont.setFamily(newValue.c_str());
this->currentFontByScale.clear();
this->fontChanged.invoke();
});
this->chatFontFamily.connect([this](const std::string &, auto) {
util::assertInGuiThread();
this->currentFontSize.connect([this](const int &newValue, auto) {
this->incGeneration();
// this->currentFont.setSize(newValue);
this->currentFontByScale.clear();
this->fontChanged.invoke();
});
}
QFont &FontManager::getFont(FontManager::Type type, float scale)
{
// return this->currentFont.getFont(type);
return this->getCurrentFont(scale).getFont(type);
}
QFontMetrics &FontManager::getFontMetrics(FontManager::Type type, float scale)
{
// return this->currentFont.getFontMetrics(type);
return this->getCurrentFont(scale).getFontMetrics(type);
}
FontManager::FontData &FontManager::Font::getFontData(FontManager::Type type)
{
switch (type) {
case Tiny:
return this->tiny;
case Small:
return this->small;
case MediumSmall:
return this->mediumSmall;
case Medium:
return this->medium;
case MediumBold:
return this->mediumBold;
case MediumItalic:
return this->mediumItalic;
case Large:
return this->large;
case VeryLarge:
return this->veryLarge;
default:
qDebug() << "Unknown font type:" << type << ", defaulting to medium";
return this->medium;
}
}
QFont &FontManager::Font::getFont(Type type)
{
return this->getFontData(type).font;
}
QFontMetrics &FontManager::Font::getFontMetrics(Type type)
{
return this->getFontData(type).metrics;
}
FontManager::Font &FontManager::getCurrentFont(float scale)
{
for (auto it = this->currentFontByScale.begin(); it != this->currentFontByScale.end(); it++) {
if (it->first == scale) {
return it->second;
if (getApp()->windows) {
getApp()->windows->incGeneration();
}
}
this->currentFontByScale.push_back(
std::make_pair(scale, Font(this->currentFontFamily.getValue().c_str(),
this->currentFontSize.getValue() * scale)));
return this->currentFontByScale.back().second;
for (auto &map : this->fontsByType) {
map.clear();
}
this->fontChanged.invoke();
});
this->chatFontSize.connect([this](const int &, auto) {
util::assertInGuiThread();
if (getApp()->windows) {
getApp()->windows->incGeneration();
}
for (auto &map : this->fontsByType) {
map.clear();
}
this->fontChanged.invoke();
});
this->fontsByType.resize(size_t(EndType));
}
QFont FontManager::getFont(FontManager::Type type, float scale)
{
return this->getOrCreateFontData(type, scale).font;
}
QFontMetrics FontManager::getFontMetrics(FontManager::Type type, float scale)
{
return this->getOrCreateFontData(type, scale).metrics;
}
FontManager::FontData &FontManager::getOrCreateFontData(Type type, float scale)
{
util::assertInGuiThread();
assert(type >= 0 && type < EndType);
auto &map = this->fontsByType[size_t(type)];
// find element
auto it = map.find(scale);
if (it != map.end()) {
// return if found
return it->second;
}
// emplace new element
auto result = map.emplace(scale, this->createFontData(type, scale));
assert(result.second);
return result.first->second;
}
FontManager::FontData FontManager::createFontData(Type type, float scale)
{
// check if it's a chat (scale the setting)
if (type >= ChatStart && type <= ChatEnd) {
static std::unordered_map<Type, ChatFontData> sizeScale{
{ChatSmall, {0.6f, false, QFont::Normal}},
{ChatMediumSmall, {0.8f, false, QFont::Normal}},
{ChatMedium, {1, false, QFont::Normal}},
{ChatMediumBold, {1, false, QFont::Medium}},
{ChatMediumItalic, {1, true, QFont::Normal}},
{ChatLarge, {1.2f, false, QFont::Normal}},
{ChatVeryLarge, {1.4f, false, QFont::Normal}},
};
auto data = sizeScale[type];
return FontData(QFont(QString::fromStdString(this->chatFontFamily.getValue()),
int(this->chatFontSize.getValue() * data.scale * scale), data.weight,
data.italic));
}
// normal Ui font (use pt size)
{
#ifdef Q_OS_MAC
constexpr float multiplier = 0.8f;
#else
constexpr float multiplier = 1.f;
#endif
static std::unordered_map<Type, UiFontData> defaultSize{
{Tiny, {8, "Monospace", false, QFont::Normal}},
{UiMedium, {int(12 * multiplier), DEFAULT_FONT_FAMILY, false, QFont::Normal}},
{UiTabs, {int(9 * multiplier), DEFAULT_FONT_FAMILY, false, QFont::Normal}},
};
UiFontData &data = defaultSize[type];
QFont font(data.name, int(data.size * scale), data.weight, data.italic);
return FontData(font);
}
}
} // namespace singletons
+43 -102
View File
@@ -3,138 +3,79 @@
#include <QFont>
#include <QFontDatabase>
#include <QFontMetrics>
#include <array>
#include <boost/noncopyable.hpp>
#include <pajlada/settings/setting.hpp>
#include <pajlada/signals/signal.hpp>
#include <unordered_map>
namespace chatterino {
namespace singletons {
class FontManager
class FontManager : boost::noncopyable
{
public:
FontManager();
FontManager(const FontManager &) = delete;
FontManager(FontManager &&) = delete;
~FontManager() = delete;
// font data gets set in createFontData(...)
enum Type : uint8_t {
Tiny,
Small,
MediumSmall,
Medium,
MediumBold,
MediumItalic,
Large,
VeryLarge,
ChatSmall,
ChatMediumSmall,
ChatMedium,
ChatMediumBold,
ChatMediumItalic,
ChatLarge,
ChatVeryLarge,
UiMedium,
UiTabs,
// don't remove this value
EndType,
// make sure to update these values accordingly!
ChatStart = ChatSmall,
ChatEnd = ChatVeryLarge,
};
QFont &getFont(Type type, float scale);
QFontMetrics &getFontMetrics(Type type, float scale);
QFont getFont(Type type, float scale);
QFontMetrics getFontMetrics(Type type, float scale);
int getGeneration() const
{
return this->generation;
}
void incGeneration()
{
this->generation++;
}
pajlada::Settings::Setting<std::string> currentFontFamily;
pajlada::Settings::Setting<int> currentFontSize;
pajlada::Settings::Setting<std::string> chatFontFamily;
pajlada::Settings::Setting<int> chatFontSize;
pajlada::Signals::NoArgSignal fontChanged;
private:
struct FontData {
FontData(QFont &&_font)
FontData(const QFont &_font)
: font(_font)
, metrics(this->font)
, metrics(_font)
{
}
QFont font;
QFontMetrics metrics;
const QFont font;
const QFontMetrics metrics;
};
struct Font {
Font() = delete;
Font(const char *fontFamilyName, int mediumSize)
: tiny(QFont("Monospace", 8))
, small(QFont(fontFamilyName, mediumSize - 4))
, mediumSmall(QFont(fontFamilyName, mediumSize - 2))
, medium(QFont(fontFamilyName, mediumSize))
, mediumBold(QFont(fontFamilyName, mediumSize, QFont::DemiBold))
, mediumItalic(QFont(fontFamilyName, mediumSize, -1, true))
, large(QFont(fontFamilyName, mediumSize))
, veryLarge(QFont(fontFamilyName, mediumSize))
{
tiny.font.setStyleHint(QFont::TypeWriter);
}
void setFamily(const char *newFamily)
{
this->small.font.setFamily(newFamily);
this->mediumSmall.font.setFamily(newFamily);
this->medium.font.setFamily(newFamily);
this->mediumBold.font.setFamily(newFamily);
this->mediumItalic.font.setFamily(newFamily);
this->large.font.setFamily(newFamily);
this->veryLarge.font.setFamily(newFamily);
this->updateMetrics();
}
void setSize(int newMediumSize)
{
this->small.font.setPointSize(newMediumSize - 4);
this->mediumSmall.font.setPointSize(newMediumSize - 2);
this->medium.font.setPointSize(newMediumSize);
this->mediumBold.font.setPointSize(newMediumSize);
this->mediumItalic.font.setPointSize(newMediumSize);
this->large.font.setPointSize(newMediumSize + 2);
this->veryLarge.font.setPointSize(newMediumSize + 4);
this->updateMetrics();
}
void updateMetrics()
{
this->small.metrics = QFontMetrics(this->small.font);
this->mediumSmall.metrics = QFontMetrics(this->mediumSmall.font);
this->medium.metrics = QFontMetrics(this->medium.font);
this->mediumBold.metrics = QFontMetrics(this->mediumBold.font);
this->mediumItalic.metrics = QFontMetrics(this->mediumItalic.font);
this->large.metrics = QFontMetrics(this->large.font);
this->veryLarge.metrics = QFontMetrics(this->veryLarge.font);
}
FontData &getFontData(Type type);
QFont &getFont(Type type);
QFontMetrics &getFontMetrics(Type type);
FontData tiny;
FontData small;
FontData mediumSmall;
FontData medium;
FontData mediumBold;
FontData mediumItalic;
FontData large;
FontData veryLarge;
struct ChatFontData {
float scale;
bool italic;
QFont::Weight weight;
};
Font &getCurrentFont(float scale);
struct UiFontData {
float size;
const char *name;
bool italic;
QFont::Weight weight;
};
// Future plans:
// Could have multiple fonts in here, such as "Menu font", "Application font", "Chat font"
FontData &getOrCreateFontData(Type type, float scale);
FontData createFontData(Type type, float scale);
std::list<std::pair<float, Font>> currentFontByScale;
int generation = 0;
std::vector<std::unordered_map<float, FontData>> fontsByType;
};
} // namespace singletons
+35 -15
View File
@@ -31,6 +31,7 @@ namespace ipc = boost::interprocess;
namespace chatterino {
namespace singletons {
// fourtf: don't add this class to the application class
NativeMessagingManager::NativeMessagingManager()
{
qDebug() << "init NativeMessagingManager";
@@ -50,6 +51,10 @@ void NativeMessagingManager::registerHost()
{
auto app = getApp();
if (app->paths->isPortable()) {
return;
}
// create manifest
QJsonDocument document;
QJsonObject root_obj;
@@ -113,13 +118,14 @@ void NativeMessagingManager::ReceiverThread::run()
while (true) {
try {
char *buf = (char *)malloc(MESSAGE_SIZE);
std::unique_ptr<char> buf(static_cast<char *>(malloc(MESSAGE_SIZE)));
ipc::message_queue::size_type retSize;
unsigned int priority;
messageQueue.receive(buf, MESSAGE_SIZE, retSize, priority);
messageQueue.receive(buf.get(), MESSAGE_SIZE, retSize, priority);
QJsonDocument document = QJsonDocument::fromJson(QByteArray(buf, retSize));
QJsonDocument document =
QJsonDocument::fromJson(QByteArray::fromRawData(buf.get(), retSize));
this->handleMessage(document.object());
} catch (ipc::interprocess_exception &ex) {
@@ -143,26 +149,37 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro
QString _type = root.value("type").toString();
bool attach = root.value("attach").toBool();
QString name = root.value("name").toString();
QString winId = root.value("winId").toString();
int yOffset = root.value("yOffset").toInt(-1);
if (_type.isNull() || name.isNull() || winId.isNull()) {
#ifdef USEWINSDK
widgets::AttachedWindow::GetArgs args;
args.winId = root.value("winId").toString();
args.yOffset = root.value("yOffset").toInt(-1);
args.width = root.value("size").toObject().value("width").toInt(-1);
args.height = root.value("size").toObject().value("height").toInt(-1);
if (_type.isNull() || args.winId.isNull()) {
qDebug() << "NM type, name or winId missing";
attach = false;
return;
}
#endif
if (_type == "twitch") {
util::postToThread([name, attach, winId, yOffset, app] {
app->twitch.server->watchingChannel.update(
app->twitch.server->getOrAddChannel(name));
util::postToThread([=] {
if (!name.isEmpty()) {
app->twitch.server->watchingChannel.update(
app->twitch.server->getOrAddChannel(name));
}
if (attach) {
#ifdef USEWINSDK
auto *window =
widgets::AttachedWindow::get(::GetForegroundWindow(), winId, yOffset);
window->setChannel(app->twitch.server->getOrAddChannel(name));
window->show();
if (args.height != -1) {
auto *window = widgets::AttachedWindow::get(::GetForegroundWindow(), args);
if (!name.isEmpty()) {
window->setChannel(app->twitch.server->getOrAddChannel(name));
}
}
// window->show();
#endif
}
});
@@ -179,12 +196,15 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro
}
#ifdef USEWINSDK
util::postToThread([winId] { widgets::AttachedWindow::detach(winId); });
util::postToThread([winId] {
qDebug() << "NW detach";
widgets::AttachedWindow::detach(winId);
});
#endif
} else {
qDebug() << "NM unknown action " + action;
}
}
} // namespace singletons
} // namespace singletons
} // namespace chatterino
@@ -8,6 +8,7 @@ namespace singletons {
class NativeMessagingManager
{
public:
// fourtf: don't add this class to the application class
NativeMessagingManager();
~NativeMessagingManager() = delete;
+10 -5
View File
@@ -19,21 +19,21 @@ PathManager::PathManager(int argc, char **argv)
.replace("/", "x");
// Options
bool portable = false;
this->portable = false;
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "portable") == 0) {
portable = true;
this->portable = true;
}
}
if (QFileInfo::exists(QCoreApplication::applicationDirPath() + "/portable")) {
portable = true;
if (QFileInfo::exists(QCoreApplication::applicationDirPath() + "/this->portable")) {
this->portable = true;
}
// Root path = %APPDATA%/Chatterino or the folder that the executable resides in
QString rootPath;
if (portable) {
if (this->portable) {
rootPath.append(QCoreApplication::applicationDirPath());
} else {
// Get settings path
@@ -91,5 +91,10 @@ bool PathManager::createFolder(const QString &folderPath)
return QDir().mkpath(folderPath);
}
bool PathManager::isPortable()
{
return this->portable;
}
} // namespace singletons
} // namespace chatterino
+4
View File
@@ -28,6 +28,10 @@ public:
QString appPathHash;
bool createFolder(const QString &folderPath);
bool isPortable();
private:
bool portable;
};
} // namespace singletons
+1
View File
@@ -2,6 +2,7 @@
#include "util/emotemap.hpp"
#include <QIcon>
#include <QRegularExpression>
#include <map>
+11 -1
View File
@@ -39,8 +39,18 @@ void SettingManager::initialize()
this->timestampFormat.connect([](auto, auto) {
auto app = getApp();
app->windows->layoutVisibleChatWidgets();
app->windows->layoutChannelViews();
});
this->emoteScale.connect([](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
this->timestampFormat.connect([](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
this->alternateMessageBackground.connect(
[](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
this->seperateMessages.connect(
[](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
this->collpseMessagesMinLines.connect(
[](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
}
MessageElement::Flags SettingManager::getWordFlags()
+8 -4
View File
@@ -40,6 +40,8 @@ public:
BoolSetting hideEmptyInput = {"/appearance/hideEmptyInputBox", false};
BoolSetting showMessageLength = {"/appearance/messages/showMessageLength", false};
BoolSetting seperateMessages = {"/appearance/messages/separateMessages", false};
// BoolSetting collapseLongMessages = {"/appearance/messages/collapseLongMessages", false};
IntSetting collpseMessagesMinLines = {"/appearance/messages/collapseMessagesMinLines", 0};
BoolSetting alternateMessageBackground = {"/appearance/messages/alternateMessageBackground",
false};
BoolSetting windowTopMost = {"/appearance/windowAlwaysOnTop", false};
@@ -54,6 +56,8 @@ public:
/// Behaviour
BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages", true};
BoolSetting mentionUsersWithAt = {"/behaviour/mentionUsersWithAt", false};
BoolSetting showJoins = {"/behaviour/showJoins", false};
BoolSetting showParts = {"/behaviour/showParts", false};
FloatSetting mouseScrollMultiplier = {"/behaviour/mouseScrollMultiplier", 1.0};
// Auto-completion
@@ -75,10 +79,10 @@ public:
BoolSetting enableGifAnimations = {"/emotes/enableGifAnimations", true};
FloatSetting emoteScale = {"/emotes/scale", 1.f};
// 0 = Smallest size
// 1 = One size above 0 (usually size of 0 * 2)
// 2 = One size above 1 (usually size of 1 * 2)
// etc...
// 0 = No preference
// 1 = 1x
// 2 = 2x
// 3 = 3x
IntSetting preferredEmoteQuality = {"/emotes/preferredEmoteQuality", 0};
/// Links
+66 -32
View File
@@ -52,7 +52,6 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
isLight = multiplier > 0;
bool lightWin = isLight;
QColor none(0, 0, 0, 0);
QColor themeColor = QColor::fromHslF(hue, 0.43, 0.5);
QColor themeColorNoSat = QColor::fromHslF(hue, 0, 0.5);
@@ -69,7 +68,7 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
#ifdef Q_OS_LINUX
this->window.background = lightWin ? "#fff" : QColor(61, 60, 56);
#else
this->window.background = lightWin ? "#fff" : "#444";
this->window.background = lightWin ? "#fff" : "#111";
#endif
QColor fg = this->window.text = lightWin ? "#000" : "#eee";
@@ -89,27 +88,52 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
/// TABS
if (lightWin) {
this->tabs.regular = {fg, {bg, QColor("#ccc"), bg}};
this->tabs.regular = {QColor("#444"),
{QColor("#fff"), QColor("#fff"), QColor("#fff")},
{QColor("#fff"), QColor("#fff"), QColor("#fff")}};
this->tabs.newMessage = {
fg,
{QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
QBrush(blendColors(themeColorNoSat, "#ccc", 0.9), Qt::FDiagPattern)}};
this->tabs.highlighted = {fg, {QColor("#ccc"), QColor("#ccc"), QColor("#bbb")}};
this->tabs.selected = {QColor("#fff"),
{QColor("#777"), QColor("#777"), QColor("#888")}};
} else {
this->tabs.regular = {fg, {bg, QColor("#555"), bg}};
this->tabs.newMessage = {
fg,
{QBrush(blendColors(themeColor, "#666", 0.7), Qt::FDiagPattern),
QBrush(blendColors(themeColor, "#666", 0.5), Qt::FDiagPattern),
QBrush(blendColors(themeColorNoSat, "#666", 0.7), Qt::FDiagPattern)}};
this->tabs.highlighted = {fg, {QColor("#777"), QColor("#777"), QColor("#666")}};
fg, {bg, QColor("#ccc"), bg}, {QColor("#aaa"), QColor("#aaa"), QColor("#aaa")}};
this->tabs.highlighted = {fg,
{bg, QColor("#ccc"), bg},
{QColor("#b60505"), QColor("#b60505"), QColor("#b60505")}};
this->tabs.selected = {QColor("#000"),
{QColor("#999"), QColor("#999"), QColor("#888")}};
{QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
} else {
this->tabs.regular = {QColor("#aaa"),
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#444"), QColor("#444"), QColor("#444")}};
this->tabs.newMessage = {fg,
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#888"), QColor("#888"), QColor("#888")}};
this->tabs.highlighted = {fg,
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#ee6166"), QColor("#ee6166"), QColor("#ee6166")}};
this->tabs.selected = {QColor("#fff"),
{QColor("#555555"), QColor("#555555"), QColor("#555555")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
}
// scrollbar
this->scrollbars.highlights.highlight = QColor("#ee6166");
this->scrollbars.highlights.subscription = QColor("#C466FF");
// this->tabs.newMessage = {
// fg,
// {QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
// QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
// QBrush(blendColors(themeColorNoSat, "#ccc", 0.9), Qt::FDiagPattern)}};
// this->tabs.newMessage = {
// fg,
// {QBrush(blendColors(themeColor, "#666", 0.7), Qt::FDiagPattern),
// QBrush(blendColors(themeColor, "#666", 0.5), Qt::FDiagPattern),
// QBrush(blendColors(themeColorNoSat, "#666", 0.7),
// Qt::FDiagPattern)}};
// this->tabs.highlighted = {fg, {QColor("#777"), QColor("#777"),
// QColor("#666")}};
this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color();
}
@@ -119,7 +143,12 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
this->splits.messageSeperator = isLight ? QColor(127, 127, 127) : QColor(60, 60, 60);
this->splits.background = getColor(0, sat, 1);
this->splits.dropPreview = QColor(0, 148, 255, 0x30);
this->splits.dropPreviewBorder = QColor(0, 148, 255, 0x70);
this->splits.dropPreviewBorder = QColor(0, 148, 255, 0xff);
this->splits.dropTargetRect = QColor(0, 148, 255, 0x00);
this->splits.dropTargetRectBorder = QColor(0, 148, 255, 0x00);
this->splits.resizeHandle = QColor(0, 148, 255, 0x70);
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x20);
// this->splits.border
// this->splits.borderFocused
@@ -143,7 +172,10 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
this->messages.backgrounds.regular = splits.background;
this->messages.backgrounds.alternate = getColor(0, sat, 0.93);
this->messages.backgrounds.highlighted =
blendColors(themeColor, this->messages.backgrounds.regular, 0.8);
blendColors(themeColor, this->messages.backgrounds.regular, 0.6);
this->messages.backgrounds.subscription =
blendColors(QColor("#C466FF"), this->messages.backgrounds.regular, 0.7);
// this->messages.backgrounds.resub
// this->messages.backgrounds.whisper
this->messages.disabled = getColor(0, sat, 1, 0.6);
@@ -151,7 +183,9 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
// this->messages.seperatorInner =
// Scrollbar
this->scrollbars.background = getColor(0, sat, 0.94);
this->scrollbars.background = QColor(0, 0, 0, 0);
// this->scrollbars.background = splits.background;
// this->scrollbars.background.setAlphaF(qreal(0.2));
this->scrollbars.thumb = getColor(0, sat, 0.80);
this->scrollbars.thumbSelected = getColor(0, sat, 0.7);
@@ -163,13 +197,13 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
this->messages.selection = isLightTheme() ? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64);
this->updated.invoke();
}
} // namespace singletons
QColor ThemeManager::blendColors(const QColor &color1, const QColor &color2, qreal ratio)
{
int r = color1.red() * (1 - ratio) + color2.red() * ratio;
int g = color1.green() * (1 - ratio) + color2.green() * ratio;
int b = color1.blue() * (1 - ratio) + color2.blue() * ratio;
int r = int(color1.red() * (1 - ratio) + color2.red() * ratio);
int g = int(color1.green() * (1 - ratio) + color2.green() * ratio);
int b = int(color1.blue() * (1 - ratio) + color2.blue() * ratio);
return QColor(r, g, b, 255);
}
@@ -177,22 +211,22 @@ QColor ThemeManager::blendColors(const QColor &color1, const QColor &color2, qre
void ThemeManager::normalizeColor(QColor &color)
{
if (this->isLight) {
if (color.lightnessF() > 0.5f) {
color.setHslF(color.hueF(), color.saturationF(), 0.5f);
if (color.lightnessF() > 0.5) {
color.setHslF(color.hueF(), color.saturationF(), 0.5);
}
if (color.lightnessF() > 0.4f && color.hueF() > 0.1 && color.hueF() < 0.33333) {
if (color.lightnessF() > 0.4 && color.hueF() > 0.1 && color.hueF() < 0.33333) {
color.setHslF(
color.hueF(), color.saturationF(),
color.lightnessF() - sin((color.hueF() - 0.1) / (0.3333 - 0.1) * 3.14159) *
color.saturationF() * 0.2);
}
} else {
if (color.lightnessF() < 0.5f) {
color.setHslF(color.hueF(), color.saturationF(), 0.5f);
if (color.lightnessF() < 0.5) {
color.setHslF(color.hueF(), color.saturationF(), 0.5);
}
if (color.lightnessF() < 0.6f && color.hueF() > 0.54444 && color.hueF() < 0.83333) {
if (color.lightnessF() < 0.6 && color.hueF() > 0.54444 && color.hueF() < 0.83333) {
color.setHslF(
color.hueF(), color.saturationF(),
color.lightnessF() + sin((color.hueF() - 0.54444) / (0.8333 - 0.54444) * 3.14159) *
+17 -6
View File
@@ -25,11 +25,16 @@ public:
struct TabColors {
QColor text;
struct Backgrounds {
struct {
QBrush regular;
QBrush hover;
QBrush unfocused;
} backgrounds;
struct {
QColor regular;
QColor hover;
QColor unfocused;
} line;
};
/// WINDOW
@@ -43,9 +48,9 @@ public:
/// TABS
struct {
TabColors regular;
TabColors selected;
TabColors highlighted;
TabColors newMessage;
TabColors highlighted;
TabColors selected;
QColor border;
QColor bottomLine;
} tabs;
@@ -58,6 +63,10 @@ public:
QColor borderFocused;
QColor dropPreview;
QColor dropPreviewBorder;
QColor dropTargetRect;
QColor dropTargetRectBorder;
QColor resizeHandle;
QColor resizeHandleBackground;
struct {
QColor border;
@@ -89,7 +98,7 @@ public:
QColor regular;
QColor alternate;
QColor highlighted;
// QColor resub;
QColor subscription;
// QColor whisper;
} backgrounds;
@@ -104,8 +113,10 @@ public:
QColor background;
QColor thumb;
QColor thumbSelected;
// const int highlightsCount = 3;
// QColor highlights[3];
struct {
QColor highlight;
QColor subscription;
} highlights;
} scrollbars;
/// TOOLTIP
+33 -6
View File
@@ -7,25 +7,31 @@ namespace chatterino {
namespace singletons {
UpdateManager::UpdateManager()
: currentVersion(CHATTERINO_VERSION)
: currentVersion_(CHATTERINO_VERSION)
{
qDebug() << "init UpdateManager";
}
UpdateManager &UpdateManager::getInstance()
{
// fourtf: don't add this class to the application class
static UpdateManager instance;
return instance;
}
const QString &UpdateManager::getCurrentVersion() const
{
return this->getCurrentVersion();
return currentVersion_;
}
const QString &UpdateManager::getOnlineVersion() const
{
return this->getOnlineVersion();
return onlineVersion_;
}
void UpdateManager::installUpdates()
{
}
void UpdateManager::checkForUpdates()
@@ -33,17 +39,38 @@ void UpdateManager::checkForUpdates()
QString url = "https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/stable";
util::NetworkRequest req(url);
req.setTimeout(20000);
req.setTimeout(30000);
req.getJSON([this](QJsonObject &object) {
QJsonValue version_val = object.value("version");
if (!version_val.isString()) {
this->setStatus_(Error);
qDebug() << "error updating";
return;
}
this->onlineVersion = version_val.toString();
this->onlineVersionUpdated.invoke();
this->onlineVersion_ = version_val.toString();
if (this->currentVersion_ != this->onlineVersion_) {
this->setStatus_(UpdateAvailable);
} else {
this->setStatus_(NoUpdateAvailable);
}
});
this->setStatus_(Searching);
req.execute();
}
UpdateManager::UpdateStatus UpdateManager::getStatus() const
{
return this->status_;
}
void UpdateManager::setStatus_(UpdateStatus status)
{
if (this->status_ != status) {
this->status_ = status;
this->statusUpdated.invoke(status);
}
}
} // namespace singletons
+11 -3
View File
@@ -11,17 +11,25 @@ class UpdateManager
UpdateManager();
public:
enum UpdateStatus { None, Searching, UpdateAvailable, NoUpdateAvailable, Error };
// fourtf: don't add this class to the application class
static UpdateManager &getInstance();
void checkForUpdates();
const QString &getCurrentVersion() const;
const QString &getOnlineVersion() const;
void installUpdates();
UpdateStatus getStatus() const;
pajlada::Signals::NoArgSignal onlineVersionUpdated;
pajlada::Signals::Signal<UpdateStatus> statusUpdated;
private:
QString currentVersion;
QString onlineVersion;
QString currentVersion_;
QString onlineVersion_;
UpdateStatus status_ = None;
void setStatus_(UpdateStatus status);
};
} // namespace singletons
+83 -29
View File
@@ -10,6 +10,7 @@
#include "widgets/accountswitchpopupwidget.hpp"
#include "widgets/settingsdialog.hpp"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
@@ -20,6 +21,9 @@
namespace chatterino {
namespace singletons {
using SplitNode = widgets::SplitContainer::Node;
using SplitDirection = widgets::SplitContainer::Direction;
void WindowManager::showSettingsDialog()
{
QTimer::singleShot(80, [] { widgets::SettingsDialog::showDialog(); });
@@ -54,11 +58,17 @@ WindowManager::WindowManager()
qDebug() << "init WindowManager";
}
void WindowManager::layoutVisibleChatWidgets(Channel *channel)
void WindowManager::layoutChannelViews(Channel *channel)
{
this->layout.invoke(channel);
}
void WindowManager::forceLayoutChannelViews()
{
this->incGeneration();
this->layoutChannelViews(nullptr);
}
void WindowManager::repaintVisibleChatWidgets(Channel *channel)
{
if (this->mainWindow != nullptr) {
@@ -184,32 +194,40 @@ void WindowManager::initialize()
// load tabs
QJsonArray tabs = window_obj.value("tabs").toArray();
for (QJsonValue tab_val : tabs) {
widgets::SplitContainer *tab = window.getNotebook().addNewPage();
widgets::SplitContainer *page = window.getNotebook().addPage(false);
QJsonObject tab_obj = tab_val.toObject();
// set custom title
QJsonValue title_val = tab_obj.value("title");
if (title_val.isString()) {
tab->getTab()->setTitle(title_val.toString());
tab->getTab()->useDefaultTitle = false;
page->getTab()->setCustomTitle(title_val.toString());
}
// selected
if (tab_obj.value("selected").toBool(false)) {
window.getNotebook().select(tab);
window.getNotebook().select(page);
}
// load splits
QJsonObject splitRoot = tab_obj.value("splits2").toObject();
if (!splitRoot.isEmpty()) {
page->decodeFromJson(splitRoot);
continue;
}
// fallback load splits (old)
int colNr = 0;
for (QJsonValue column_val : tab_obj.value("splits").toArray()) {
for (QJsonValue split_val : column_val.toArray()) {
widgets::Split *split = new widgets::Split(tab);
widgets::Split *split = new widgets::Split(page);
QJsonObject split_obj = split_val.toObject();
split->setChannel(this->decodeChannel(split_obj));
split->setChannel(decodeChannel(split_obj));
tab->addToLayout(split, std::make_pair(colNr, 10000000));
page->appendSplit(split);
}
colNr++;
}
@@ -218,7 +236,7 @@ void WindowManager::initialize()
if (mainWindow == nullptr) {
mainWindow = &createWindow(widgets::Window::Main);
mainWindow->getNotebook().addNewPage(true);
mainWindow->getNotebook().addPage(true);
}
this->initialized = true;
@@ -255,13 +273,15 @@ void WindowManager::save()
// window tabs
QJsonArray tabs_arr;
for (int tab_i = 0; tab_i < window->getNotebook().tabCount(); tab_i++) {
for (int tab_i = 0; tab_i < window->getNotebook().getPageCount(); tab_i++) {
QJsonObject tab_obj;
widgets::SplitContainer *tab = window->getNotebook().tabAt(tab_i);
widgets::SplitContainer *tab =
dynamic_cast<widgets::SplitContainer *>(window->getNotebook().getPageAt(tab_i));
assert(tab != nullptr);
// custom tab title
if (!tab->getTab()->useDefaultTitle) {
tab_obj.insert("title", tab->getTab()->getTitle());
if (tab->getTab()->hasCustomTitle()) {
tab_obj.insert("title", tab->getTab()->getCustomTitle());
}
// selected
@@ -270,23 +290,11 @@ void WindowManager::save()
}
// splits
QJsonArray columns_arr;
std::vector<std::vector<widgets::Split *>> columns = tab->getColumns();
QJsonObject splits;
for (std::vector<widgets::Split *> &cells : columns) {
QJsonArray cells_arr;
this->encodeNodeRecusively(tab->getBaseNode(), splits);
for (widgets::Split *cell : cells) {
QJsonObject cell_obj;
this->encodeChannel(cell->getIndirectChannel(), cell_obj);
cells_arr.append(cell_obj);
}
columns_arr.append(cells_arr);
}
tab_obj.insert("splits", columns_arr);
tab_obj.insert("splits2", splits);
tabs_arr.append(tab_obj);
}
@@ -302,10 +310,46 @@ void WindowManager::save()
QString settingsPath = app->paths->settingsFolderPath + SETTINGS_FILENAME;
QFile file(settingsPath);
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
file.write(document.toJson());
QJsonDocument::JsonFormat format =
#ifdef _DEBUG
QJsonDocument::JsonFormat::Compact
#else
(QJsonDocument::JsonFormat)0
#endif
;
file.write(document.toJson(format));
file.flush();
}
void WindowManager::encodeNodeRecusively(SplitNode *node, QJsonObject &obj)
{
switch (node->getType()) {
case SplitNode::_Split: {
obj.insert("type", "split");
QJsonObject split;
encodeChannel(node->getSplit()->getIndirectChannel(), split);
obj.insert("data", split);
obj.insert("flexh", node->getHorizontalFlex());
obj.insert("flexv", node->getVerticalFlex());
} break;
case SplitNode::HorizontalContainer:
case SplitNode::VerticalContainer: {
obj.insert("type", node->getType() == SplitNode::HorizontalContainer ? "horizontal"
: "vertical");
QJsonArray items_arr;
for (const std::unique_ptr<SplitNode> &n : node->getChildren()) {
QJsonObject subObj;
this->encodeNodeRecusively(n.get(), subObj);
items_arr.append(subObj);
}
obj.insert("items", items_arr);
} break;
}
}
void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj)
{
util::assertInGuiThread();
@@ -356,5 +400,15 @@ void WindowManager::closeAll()
}
}
int WindowManager::getGeneration() const
{
return this->generation;
}
void WindowManager::incGeneration()
{
this->generation++;
}
} // namespace singletons
} // namespace chatterino
+16 -3
View File
@@ -1,8 +1,12 @@
#pragma once
#include "widgets/splitcontainer.hpp"
#include "widgets/window.hpp"
namespace chatterino {
// namespace widgets {
// struct SplitContainer::Node;
//}
namespace singletons {
class WindowManager
@@ -15,7 +19,8 @@ public:
void showSettingsDialog();
void showAccountSelectPopup(QPoint point);
void layoutVisibleChatWidgets(Channel *channel = nullptr);
void layoutChannelViews(Channel *channel = nullptr);
void forceLayoutChannelViews();
void repaintVisibleChatWidgets(Channel *channel = nullptr);
void repaintGifEmotes();
// void updateAll();
@@ -31,19 +36,27 @@ public:
void initialize();
void closeAll();
int getGeneration() const;
void incGeneration();
pajlada::Signals::NoArgSignal repaintGifs;
pajlada::Signals::Signal<Channel *> layout;
private:
bool initialized = false;
std::atomic<int> generation{0};
std::vector<widgets::Window *> windows;
widgets::Window *mainWindow = nullptr;
widgets::Window *selectedWindow = nullptr;
void encodeChannel(IndirectChannel channel, QJsonObject &obj);
IndirectChannel decodeChannel(const QJsonObject &obj);
void encodeNodeRecusively(widgets::SplitContainer::Node *node, QJsonObject &obj);
public:
static void encodeChannel(IndirectChannel channel, QJsonObject &obj);
static IndirectChannel decodeChannel(const QJsonObject &obj);
};
} // namespace singletons
+25 -1
View File
@@ -3,6 +3,7 @@
#include <QDebug>
#include <QElapsedTimer>
#include <boost/current_function.hpp>
#include <boost/noncopyable.hpp>
#define BENCH(x) \
QElapsedTimer x; \
@@ -10,4 +11,27 @@
#define MARK(x) \
qDebug() << BOOST_CURRENT_FUNCTION << __LINE__ \
<< static_cast<float>(x.nsecsElapsed()) / 100000.0 << "ms";
<< static_cast<float>(x.nsecsElapsed()) / 1000000.0 << "ms";
class BenchmarkGuard : boost::noncopyable
{
QElapsedTimer timer;
QString name;
public:
BenchmarkGuard(const QString &_name)
: name(_name)
{
timer.start();
}
~BenchmarkGuard()
{
qDebug() << this->name << float(timer.nsecsElapsed()) / 1000000.0f << "ms";
}
qreal getElapsedMs()
{
return qreal(timer.nsecsElapsed()) / 1000000.0;
}
};
+48
View File
@@ -0,0 +1,48 @@
#include "emotemap.hpp"
#include "application.hpp"
#include "singletons/settingsmanager.hpp"
namespace chatterino {
namespace util {
EmoteData::EmoteData(messages::Image *_image)
: image1x(_image)
{
}
// Emotes must have a 1x image to be valid
bool EmoteData::isValid() const
{
return this->image1x != nullptr;
}
messages::Image *EmoteData::getImage(float scale) const
{
int quality = getApp()->settings->preferredEmoteQuality;
if (quality == 0) {
scale *= getApp()->settings->emoteScale.getValue();
quality = [&] {
if (scale <= 1)
return 1;
if (scale <= 2)
return 2;
return 3;
}();
}
messages::Image *_image;
if (quality == 3 && this->image3x != nullptr) {
_image = this->image3x;
} else if (quality >= 2 && this->image2x != nullptr) {
_image = this->image2x;
} else {
_image = this->image1x;
}
return _image;
}
} // namespace util
} // namespace chatterino
+6 -8
View File
@@ -9,20 +9,18 @@ namespace util {
struct EmoteData {
EmoteData() = default;
EmoteData(messages::Image *_image)
: image1x(_image)
{
}
EmoteData(messages::Image *_image);
// Emotes must have a 1x image to be valid
bool isValid() const
{
return this->image1x != nullptr;
}
bool isValid() const;
messages::Image *getImage(float scale) const;
messages::Image *image1x = nullptr;
messages::Image *image2x = nullptr;
messages::Image *image3x = nullptr;
// Link to the emote page i.e. https://www.frankerfacez.com/emoticon/144722-pajaCringe
QString pageLink;
};
using EmoteMap = ConcurrentMap<QString, EmoteData>;
+1 -1
View File
@@ -5,7 +5,7 @@
namespace chatterino {
namespace util {
inline QString ParseTagString(const QString &input)
inline QString parseTagString(const QString &input)
{
QString output = input;
output.detach();
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include <mutex>
namespace chatterino {
namespace util {
template <typename T>
class MutexValue
{
mutable std::mutex mutex;
T value;
public:
MutexValue()
{
}
MutexValue(T &&val)
: value(val)
{
}
T get() const
{
std::lock_guard<std::mutex> guard(this->mutex);
return this->value;
}
void set(const T &val)
{
std::lock_guard<std::mutex> guard(this->mutex);
this->value = val;
}
};
} // namespace util
} // namespace chatterino
+29 -25
View File
@@ -216,7 +216,7 @@ public:
if (this->data.caller != nullptr) {
QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller,
[ onFinished, data = this->data ](auto reply) mutable {
[onFinished, data = this->data](auto reply) mutable {
if (reply->error() != QNetworkReply::NetworkError::NoError) {
// TODO: We might want to call an onError callback here
return;
@@ -238,7 +238,7 @@ public:
QObject::connect(
&requester, &NetworkRequester::requestUrl, worker,
[ timer, data = std::move(this->data), worker, onFinished{std::move(onFinished)} ]() {
[timer, data = std::move(this->data), worker, onFinished{std::move(onFinished)}]() {
QNetworkReply *reply = NetworkManager::NaM.get(data.request);
if (timer != nullptr) {
@@ -253,21 +253,21 @@ public:
data.onReplyCreated(reply);
}
QObject::connect(reply, &QNetworkReply::finished, worker, [
data = std::move(data), worker, reply, onFinished = std::move(onFinished)
]() mutable {
if (data.caller == nullptr) {
QByteArray bytes = reply->readAll();
data.writeToCache(bytes);
onFinished(bytes);
QObject::connect(reply, &QNetworkReply::finished, worker,
[data = std::move(data), worker, reply,
onFinished = std::move(onFinished)]() mutable {
if (data.caller == nullptr) {
QByteArray bytes = reply->readAll();
data.writeToCache(bytes);
onFinished(bytes);
reply->deleteLater();
} else {
emit worker->doneUrl(reply);
}
reply->deleteLater();
} else {
emit worker->doneUrl(reply);
}
delete worker;
});
delete worker;
});
});
emit requester.requestUrl();
@@ -276,7 +276,7 @@ public:
template <typename FinishedCallback>
void getJSON(FinishedCallback onFinished)
{
this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes)->bool {
this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes) -> bool {
auto object = parseJSONFromData(bytes);
onFinished(object);
@@ -289,7 +289,7 @@ public:
template <typename FinishedCallback>
void getJSON2(FinishedCallback onFinished)
{
this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes)->bool {
this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes) -> bool {
auto object = parseJSONFromData2(bytes);
onFinished(object);
@@ -303,6 +303,7 @@ public:
{
switch (this->data.requestType) {
case GetRequest: {
debug::Log("Call GET request!");
this->executeGet();
} break;
@@ -369,10 +370,8 @@ private:
worker->moveToThread(&NetworkManager::workerThread);
if (this->data.caller != nullptr) {
QObject::connect(worker, &NetworkWorker::doneUrl,
this->data.caller, [data = this->data](auto reply) mutable {
auto &dat = data;
QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller,
[data = this->data](auto reply) mutable {
if (reply->error() != QNetworkReply::NetworkError::NoError) {
if (data.onError) {
data.onError(reply->error());
@@ -395,8 +394,8 @@ private:
}
QObject::connect(&requester, &NetworkRequester::requestUrl, worker,
[ timer, data = std::move(this->data), worker ]() {
QNetworkReply *reply;
[timer, data = std::move(this->data), worker]() {
QNetworkReply *reply = nullptr;
switch (data.requestType) {
case GetRequest: {
reply = NetworkManager::NaM.get(data.request);
@@ -431,11 +430,16 @@ private:
}
QObject::connect(reply, &QNetworkReply::finished, worker,
[ data = std::move(data), worker, reply ]() mutable {
[data = std::move(data), worker, reply]() mutable {
if (data.caller == nullptr) {
QByteArray bytes = reply->readAll();
data.writeToCache(bytes);
data.onSuccess(parseJSONFromData2(bytes));
if (data.onSuccess) {
data.onSuccess(parseJSONFromData2(bytes));
} else {
qWarning() << "data.onSuccess not found";
}
reply->deleteLater();
} else {
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <QHash>
#include <QString>
namespace std {
template <>
struct hash<QString> {
std::size_t operator()(const QString &s) const
{
return qHash(s);
}
};
} // namespace std
+17 -17
View File
@@ -11,6 +11,13 @@
namespace chatterino {
namespace util {
template <typename TVectorItem>
struct SignalVectorItemArgs {
const TVectorItem &item;
int index;
void *caller;
};
template <typename TVectorItem>
class ReadOnlySignalVector : boost::noncopyable
{
@@ -24,14 +31,8 @@ public:
}
virtual ~ReadOnlySignalVector() = default;
struct ItemArgs {
const TVectorItem &item;
int index;
void *caller;
};
pajlada::Signals::Signal<ItemArgs> itemInserted;
pajlada::Signals::Signal<ItemArgs> itemRemoved;
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemInserted;
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
pajlada::Signals::NoArgSignal delayedItemsChanged;
const std::vector<TVectorItem> &getVector() const
@@ -69,7 +70,7 @@ public:
TVectorItem item = this->vector[index];
this->vector.erase(this->vector.begin() + index);
typename ReadOnlySignalVector<TVectorItem>::ItemArgs args{item, index, caller};
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemRemoved.invoke(args);
this->invokeDelayedItemsChanged();
@@ -96,7 +97,7 @@ public:
this->vector.insert(this->vector.begin() + index, item);
typename ReadOnlySignalVector<TVectorItem>::ItemArgs args{item, index, caller};
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemInserted.invoke(args);
this->invokeDelayedItemsChanged();
return index;
@@ -107,16 +108,15 @@ template <typename TVectorItem, typename Compare>
class SortedSignalVector : public BaseSignalVector<TVectorItem>
{
public:
virtual int insertItem(const TVectorItem &item, int proposedIndex = -1,
void *caller = 0) override
virtual int insertItem(const TVectorItem &item, int = -1, void *caller = nullptr) override
{
util::assertInGuiThread();
int index =
this->vector.insert(
std::lower_bound(this->vector.begin(), this->vector.end(), item, Compare{}), item) -
this->vector.begin();
typename ReadOnlySignalVector<TVectorItem>::ItemArgs args{item, index, caller};
auto it = std::lower_bound(this->vector.begin(), this->vector.end(), item, Compare{});
int index = it - this->vector.begin();
this->vector.insert(it, item);
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemInserted.invoke(args);
this->invokeDelayedItemsChanged();
return index;
-1
View File
@@ -1 +0,0 @@
#include "signalvectormodel.hpp"
+38 -17
View File
@@ -2,6 +2,7 @@
#include <QAbstractTableModel>
#include <QStandardItem>
#include <boost/optional.hpp>
#include <util/signalvector2.hpp>
#include <pajlada/signals/signalholder.hpp>
@@ -26,11 +27,7 @@ public:
{
this->vector = vec;
auto insert = [this](const typename BaseSignalVector<TVectorItem>::ItemArgs &args) {
if (args.caller == this) {
return;
}
auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) {
// get row index
int index = this->getModelIndexFromVectorIndex(args.index);
assert(index >= 0 && index <= this->rows.size());
@@ -43,13 +40,13 @@ public:
index = this->beforeInsert(args.item, row, index);
this->beginInsertRows(QModelIndex(), index, index);
this->rows.insert(this->rows.begin() + index, Row(row));
this->rows.insert(this->rows.begin() + index, Row(row, args.item));
this->endInsertRows();
};
int i = 0;
for (const TVectorItem &item : vec->getVector()) {
typename BaseSignalVector<TVectorItem>::ItemArgs args{item, i++, 0};
SignalVectorItemArgs<TVectorItem> args{item, i++, 0};
insert(args);
}
@@ -57,20 +54,21 @@ public:
this->managedConnect(vec->itemInserted, insert);
this->managedConnect(vec->itemRemoved, [this](auto args) {
if (args.caller == this) {
return;
}
int row = this->getModelIndexFromVectorIndex(args.index);
assert(row >= 0 && row <= this->rows.size());
// remove row
std::vector<QStandardItem *> items = std::move(this->rows[row].items);
this->beginRemoveRows(QModelIndex(), row, row);
for (QStandardItem *item : this->rows[row].items) {
delete item;
}
this->rows.erase(this->rows.begin() + row);
this->endRemoveRows();
this->afterRemoved(args.item, items, row);
for (QStandardItem *item : items) {
delete item;
}
});
this->afterInit();
@@ -117,7 +115,10 @@ public:
} else {
int vecRow = this->getVectorIndexFromModelIndex(row);
this->vector->removeItem(vecRow, this);
TVectorItem item = this->getItemFromRow(this->rows[row].items);
assert(this->rows[row].original);
TVectorItem item =
this->getItemFromRow(this->rows[row].items, this->rows[row].original.get());
this->vector->insertItem(item, vecRow, this);
}
@@ -181,7 +182,7 @@ public:
assert(row >= 0 && row < this->rows.size());
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
this->vector->removeItem(signalVectorRow);
this->vector->removeItem(signalVectorRow, this);
return true;
}
@@ -192,7 +193,8 @@ protected:
}
// turn a vector item into a model row
virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row) = 0;
virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row,
const TVectorItem &original) = 0;
// turns a row in the model into a vector item
virtual void getRowFromItem(const TVectorItem &item, std::vector<QStandardItem *> &row) = 0;
@@ -221,6 +223,16 @@ protected:
this->endInsertRows();
}
void removeCustomRow(int index)
{
assert(index >= 0 && index <= this->rows.size());
assert(this->rows[index].isCustomRow);
this->beginRemoveRows(QModelIndex(), index, index);
this->rows.erase(this->rows.begin() + index);
this->endRemoveRows();
}
std::vector<QStandardItem *> createRow()
{
std::vector<QStandardItem *> row;
@@ -232,6 +244,7 @@ protected:
struct Row {
std::vector<QStandardItem *> items;
boost::optional<TVectorItem> original;
bool isCustomRow;
Row(std::vector<QStandardItem *> _items, bool _isCustomRow = false)
@@ -239,6 +252,14 @@ protected:
, isCustomRow(_isCustomRow)
{
}
Row(std::vector<QStandardItem *> _items, const TVectorItem &_original,
bool _isCustomRow = false)
: items(std::move(_items))
, original(_original)
, isCustomRow(_isCustomRow)
{
}
};
std::vector<Row> rows;
+3 -3
View File
@@ -1,8 +1,8 @@
#pragma once
#include "controllers/accounts/accountcontroller.hpp"
#include "credentials.hpp"
#include "debug/log.hpp"
#include "singletons/accountmanager.hpp"
#include "util/networkmanager.hpp"
#include "util/networkrequest.hpp"
@@ -100,7 +100,7 @@ static void put(QUrl url, std::function<void(QJsonObject)> successCallback)
{
QNetworkRequest request(url);
auto currentTwitchUser = getApp()->accounts->Twitch.getCurrent();
auto currentTwitchUser = getApp()->accounts->twitch.getCurrent();
QByteArray oauthToken;
if (currentTwitchUser) {
oauthToken = currentTwitchUser->getOAuthToken().toUtf8();
@@ -130,7 +130,7 @@ static void sendDelete(QUrl url, std::function<void()> successCallback)
{
QNetworkRequest request(url);
auto currentTwitchUser = getApp()->accounts->Twitch.getCurrent();
auto currentTwitchUser = getApp()->accounts->twitch.getCurrent();
QByteArray oauthToken;
if (currentTwitchUser) {
oauthToken = currentTwitchUser->getOAuthToken().toUtf8();
+11 -12
View File
@@ -3,7 +3,6 @@
#include "application.hpp"
#include "channel.hpp"
#include "credentials.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/settingsmanager.hpp"
#include "ui_accountpopupform.h"
#include "util/urlfetch.hpp"
@@ -40,8 +39,8 @@ AccountPopupWidget::AccountPopupWidget(ChannelPtr _channel)
connect(this, &AccountPopupWidget::refreshButtons, this,
&AccountPopupWidget::actuallyRefreshButtons, Qt::QueuedConnection);
app->accounts->Twitch.currentUserChanged.connect([=] {
auto currentTwitchUser = app->accounts->Twitch.getCurrent();
app->accounts->twitch.currentUserChanged.connect([this] {
auto currentTwitchUser = getApp()->accounts->twitch.getCurrent();
if (!currentTwitchUser) {
// No twitch user set (should never happen)
return;
@@ -113,7 +112,7 @@ AccountPopupWidget::AccountPopupWidget(ChannelPtr _channel)
});
QObject::connect(this->ui->ignore, &QPushButton::clicked, this, [=]() {
auto currentUser = getApp()->accounts->Twitch.getCurrent();
auto currentUser = getApp()->accounts->twitch.getCurrent();
if (!this->relationship.isIgnoring()) {
currentUser->ignoreByID(this->popupWidgetUser.userID, this->popupWidgetUser.username,
@@ -121,11 +120,11 @@ AccountPopupWidget::AccountPopupWidget(ChannelPtr _channel)
switch (result) {
case IgnoreResult_Success: {
this->relationship.setIgnoring(true);
emit refreshButtons();
emit this->refreshButtons();
} break;
case IgnoreResult_AlreadyIgnored: {
this->relationship.setIgnoring(true);
emit refreshButtons();
emit this->refreshButtons();
} break;
case IgnoreResult_Failed: {
} break;
@@ -137,7 +136,7 @@ AccountPopupWidget::AccountPopupWidget(ChannelPtr _channel)
switch (result) {
case UnignoreResult_Success: {
this->relationship.setIgnoring(false);
emit refreshButtons();
emit this->refreshButtons();
} break;
case UnignoreResult_Failed: {
} break;
@@ -225,12 +224,12 @@ void AccountPopupWidget::getUserData()
});
auto app = getApp();
auto currentUser = app->accounts->Twitch.getCurrent();
auto currentUser = app->accounts->twitch.getCurrent();
currentUser->checkFollow(this->popupWidgetUser.userID, [=](auto result) {
this->relationship.setFollowing(result == FollowResult_Following);
emit refreshButtons();
emit this->refreshButtons();
});
bool isIgnoring = false;
@@ -273,9 +272,9 @@ void AccountPopupWidget::loadAvatar(const QUrl &avatarUrl)
void AccountPopupWidget::scaleChangedEvent(float newDpi)
{
this->setStyleSheet(QString("* { font-size: <font-size>px; }")
.replace("<font-size>", QString::number((int)(12 * newDpi))));
.replace("<font-size>", QString::number(int(12 * newDpi))));
this->ui->lblAvatar->setFixedSize((int)(100 * newDpi), (int)(100 * newDpi));
this->ui->lblAvatar->setFixedSize(int(100 * newDpi), int(100 * newDpi));
}
void AccountPopupWidget::updateButtons(QWidget *layout, bool state)
@@ -302,7 +301,7 @@ void AccountPopupWidget::sendCommand(QPushButton *button, QString command)
void AccountPopupWidget::refreshLayouts()
{
auto currentTwitchUser = getApp()->accounts->Twitch.getCurrent();
auto currentTwitchUser = getApp()->accounts->twitch.getCurrent();
if (!currentTwitchUser) {
// No twitch user set (should never happen)
return;
+2 -1
View File
@@ -19,9 +19,10 @@ class Channel;
namespace widgets {
class AccountPopupWidget : public BaseWindow
class AccountPopupWidget final : public BaseWindow
{
Q_OBJECT
public:
AccountPopupWidget(ChannelPtr _channel);
+5 -2
View File
@@ -36,6 +36,8 @@ AccountSwitchPopupWidget::AccountSwitchPopupWidget(QWidget *parent)
});
this->setLayout(vbox);
// this->setStyleSheet("background: #333");
}
void AccountSwitchPopupWidget::refresh()
@@ -48,11 +50,12 @@ void AccountSwitchPopupWidget::focusOutEvent(QFocusEvent *)
this->hide();
}
void AccountSwitchPopupWidget::paintEvent(QPaintEvent *event)
void AccountSwitchPopupWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.fillRect(this->rect(), QColor(255, 255, 255));
painter.setPen(QColor("#999"));
painter.drawRect(0, 0, this->width() - 1, this->height() - 1);
}
} // namespace widgets
+7 -7
View File
@@ -2,7 +2,7 @@
#include "application.hpp"
#include "const.hpp"
#include "singletons/accountmanager.hpp"
#include "controllers/accounts/accountcontroller.hpp"
namespace chatterino {
namespace widgets {
@@ -14,18 +14,18 @@ AccountSwitchWidget::AccountSwitchWidget(QWidget *parent)
this->addItem(ANONYMOUS_USERNAME_LABEL);
for (const auto &userName : app->accounts->Twitch.getUsernames()) {
for (const auto &userName : app->accounts->twitch.getUsernames()) {
this->addItem(userName);
}
app->accounts->Twitch.userListUpdated.connect([=]() {
app->accounts->twitch.userListUpdated.connect([=]() {
this->blockSignals(true);
this->clear();
this->addItem(ANONYMOUS_USERNAME_LABEL);
for (const auto &userName : app->accounts->Twitch.getUsernames()) {
for (const auto &userName : app->accounts->twitch.getUsernames()) {
this->addItem(userName);
}
@@ -40,9 +40,9 @@ AccountSwitchWidget::AccountSwitchWidget(QWidget *parent)
if (!this->selectedItems().isEmpty()) {
QString newUsername = this->currentItem()->text();
if (newUsername.compare(ANONYMOUS_USERNAME_LABEL, Qt::CaseInsensitive) == 0) {
app->accounts->Twitch.currentUsername = "";
app->accounts->twitch.currentUsername = "";
} else {
app->accounts->Twitch.currentUsername = newUsername.toStdString();
app->accounts->twitch.currentUsername = newUsername.toStdString();
}
}
});
@@ -61,7 +61,7 @@ void AccountSwitchWidget::refreshSelection()
if (this->count() > 0) {
auto app = getApp();
auto currentUser = app->accounts->Twitch.getCurrent();
auto currentUser = app->accounts->twitch.getCurrent();
if (currentUser->isAnon()) {
this->setCurrentRow(0);
+121 -35
View File
@@ -1,14 +1,16 @@
#include "attachedwindow.hpp"
#include "application.hpp"
#include "util/debugcount.hpp"
#include "widgets/split.hpp"
#include <QTimer>
#include <QVBoxLayout>
#include "widgets/split.hpp"
#ifdef USEWINSDK
#include "Windows.h"
// don't even think about reordering these
#include "Psapi.h"
#pragma comment(lib, "Dwmapi.lib")
#endif
@@ -17,17 +19,19 @@ namespace widgets {
AttachedWindow::AttachedWindow(void *_target, int _yOffset)
: QWidget(nullptr, Qt::FramelessWindowHint | Qt::Window)
, target(_target)
, yOffset(_yOffset)
, target_(_target)
, yOffset_(_yOffset)
{
QLayout *layout = new QVBoxLayout(this);
layout->setMargin(0);
this->setLayout(layout);
auto *split = new Split(this);
this->ui.split = split;
this->ui_.split = split;
split->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding);
layout->addWidget(split);
util::DebugCount::increase("attached window");
}
AttachedWindow::~AttachedWindow()
@@ -38,18 +42,51 @@ AttachedWindow::~AttachedWindow()
break;
}
}
util::DebugCount::decrease("attached window");
}
AttachedWindow *AttachedWindow::get(void *target, const QString &winId, int yOffset)
AttachedWindow *AttachedWindow::get(void *target, const GetArgs &args)
{
for (Item &item : items) {
if (item.hwnd == target) {
return item.window;
AttachedWindow *window = [&]() {
for (Item &item : items) {
if (item.hwnd == target) {
return item.window;
}
}
auto *window = new AttachedWindow(target, args.yOffset);
items.push_back(Item{target, window, args.winId});
return window;
}();
bool show = true;
QSize size = window->size();
if (args.height != -1) {
if (args.height == 0) {
window->hide();
show = false;
} else {
window->height_ = args.height;
size.setHeight(args.height);
}
}
if (args.width != -1) {
if (args.width == 0) {
window->hide();
show = false;
} else {
window->width_ = args.width;
size.setWidth(args.width);
}
}
auto *window = new AttachedWindow(target, yOffset);
items.push_back(Item{target, window, winId});
if (show) {
window->updateWindowRect_(window->target_);
window->show();
}
return window;
}
@@ -64,43 +101,92 @@ void AttachedWindow::detach(const QString &winId)
void AttachedWindow::setChannel(ChannelPtr channel)
{
this->ui.split->setChannel(channel);
this->ui_.split->setChannel(channel);
}
void AttachedWindow::showEvent(QShowEvent *)
{
attachToHwnd(this->target);
attachToHwnd_(this->target_);
}
void AttachedWindow::attachToHwnd(void *_hwnd)
void AttachedWindow::attachToHwnd_(void *_attachedPtr)
{
#ifdef USEWINSDK
QTimer *timer = new QTimer(this);
timer->setInterval(1);
if (this->attached_) {
return;
}
HWND hwnd = (HWND)this->winId();
HWND attached = (HWND)_hwnd;
QObject::connect(timer, &QTimer::timeout, [this, hwnd, attached, timer] {
::SetLastError(0);
RECT xD;
::GetWindowRect(attached, &xD);
this->attached_ = true;
this->timer_.setInterval(1);
if (::GetLastError() != 0) {
timer->stop();
timer->deleteLater();
this->deleteLater();
auto hwnd = HWND(this->winId());
auto attached = HWND(_attachedPtr);
QObject::connect(&this->timer_, &QTimer::timeout, [this, hwnd, attached] {
// check process id
if (!this->validProcessName_) {
DWORD processId;
::GetWindowThreadProcessId(attached, &processId);
HANDLE process =
::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, processId);
std::unique_ptr<TCHAR[]> filename(new TCHAR[512]);
DWORD filenameLength = ::GetModuleFileNameEx(process, nullptr, filename.get(), 512);
QString qfilename = QString::fromWCharArray(filename.get(), filenameLength);
if (!qfilename.endsWith("chrome.exe")) {
qDebug() << "NM Illegal caller" << qfilename;
this->timer_.stop();
this->deleteLater();
return;
}
this->validProcessName_ = true;
}
HWND next = ::GetNextWindow(attached, GW_HWNDPREV);
::SetWindowPos(hwnd, next ? next : HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
::MoveWindow(hwnd, xD.right - 360, xD.top + this->yOffset - 8, 360 - 8,
xD.bottom - xD.top - this->yOffset, false);
// ::MoveWindow(hwnd, xD.right - 360, xD.top + 82, 360 - 8, xD.bottom - xD.top - 82 -
// 8,
// false);
this->updateWindowRect_(attached);
});
timer->start();
this->timer_.start();
#endif
}
void AttachedWindow::updateWindowRect_(void *_attachedPtr)
{
#ifdef USEWINSDK
auto hwnd = HWND(this->winId());
auto attached = HWND(_attachedPtr);
// We get the window rect first so we can close this window when it returns an error.
// If we query the process first and check the filename then it will return and empty string
// that doens't match.
::SetLastError(0);
RECT rect;
::GetWindowRect(attached, &rect);
if (::GetLastError() != 0) {
qDebug() << "NM GetLastError()" << ::GetLastError();
this->timer_.stop();
this->deleteLater();
return;
}
// set the correct z-order
HWND next = ::GetNextWindow(attached, GW_HWNDPREV);
::SetWindowPos(hwnd, next ? next : HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
if (this->height_ == -1) {
// ::MoveWindow(hwnd, rect.right - this->width_ - 8, rect.top + this->yOffset_ - 8,
// this->width_, rect.bottom - rect.top - this->yOffset_, false);
} else {
::MoveWindow(hwnd, rect.right - this->width_ - 8, rect.bottom - this->height_ - 8,
this->width_, this->height_, false);
}
// ::MoveWindow(hwnd, rect.right - 360, rect.top + 82, 360 - 8, rect.bottom -
// rect.top - 82 - 8, false);
#endif
}
+21 -7
View File
@@ -10,12 +10,19 @@ namespace widgets {
class AttachedWindow : public QWidget
{
AttachedWindow(void *target, int asdf);
AttachedWindow(void *target_, int asdf);
public:
~AttachedWindow();
struct GetArgs {
QString winId;
int yOffset = -1;
int width = -1;
int height = -1;
};
static AttachedWindow *get(void *target, const QString &winId, int yOffset);
virtual ~AttachedWindow() override;
static AttachedWindow *get(void *target_, const GetArgs &args);
static void detach(const QString &winId);
void setChannel(ChannelPtr channel);
@@ -26,14 +33,21 @@ protected:
// override;
private:
void *target;
int yOffset;
void *target_;
int yOffset_;
int currentYOffset_;
int width_ = 360;
int height_ = -1;
bool validProcessName_ = false;
bool attached_ = false;
QTimer timer_;
struct {
Split *split;
} ui;
} ui_;
void attachToHwnd(void *hwnd);
void attachToHwnd_(void *attached);
void updateWindowRect_(void *attached);
struct Item {
void *hwnd;
-1
View File
@@ -27,7 +27,6 @@ BaseWidget::~BaseWidget()
float BaseWidget::getScale() const
{
// return 1.f;
BaseWidget *baseWidget = dynamic_cast<BaseWidget *>(this->window());
if (baseWidget == nullptr) {
+2 -2
View File
@@ -18,7 +18,7 @@ class BaseWidget : public QWidget
public:
explicit BaseWidget(QWidget *parent, Qt::WindowFlags f = Qt::WindowFlags());
virtual ~BaseWidget();
virtual ~BaseWidget() override;
float getScale() const;
pajlada::Signals::Signal<float> scaleChanged;
@@ -26,7 +26,7 @@ public:
QSize getScaleIndependantSize() const;
int getScaleIndependantWidth() const;
int getScaleIndependantHeight() const;
void setScaleIndependantSize(int width, int yOffset);
void setScaleIndependantSize(int width, int height);
void setScaleIndependantSize(QSize);
void setScaleIndependantWidth(int value);
void setScaleIndependantHeight(int value);
+117 -74
View File
@@ -32,17 +32,28 @@
namespace chatterino {
namespace widgets {
BaseWindow::BaseWindow(QWidget *parent, bool _enableCustomFrame)
: BaseWidget(parent, Qt::Window)
, enableCustomFrame(_enableCustomFrame)
BaseWindow::BaseWindow(QWidget *parent, Flags _flags)
: BaseWidget(parent,
Qt::Window | ((_flags & TopMost) ? Qt::WindowStaysOnTopHint : (Qt::WindowFlags)0))
, enableCustomFrame(_flags & EnableCustomFrame)
, frameless(_flags & FrameLess)
, flags(_flags)
{
if (this->frameless) {
this->enableCustomFrame = false;
this->setWindowFlag(Qt::FramelessWindowHint);
}
this->init();
}
BaseWindow::Flags BaseWindow::getFlags()
{
return this->flags;
}
void BaseWindow::init()
{
auto app = getApp();
this->setWindowIcon(QIcon(":/images/icon.png"));
#ifdef USEWINSDK
@@ -53,55 +64,57 @@ void BaseWindow::init()
layout->setSpacing(0);
this->setLayout(layout);
{
QHBoxLayout *buttonLayout = this->ui.titlebarBox = new QHBoxLayout();
buttonLayout->setMargin(0);
layout->addLayout(buttonLayout);
if (!this->frameless) {
QHBoxLayout *buttonLayout = this->ui.titlebarBox = new QHBoxLayout();
buttonLayout->setMargin(0);
layout->addLayout(buttonLayout);
// title
QLabel *title = new QLabel(" Chatterino");
QObject::connect(this, &QWidget::windowTitleChanged,
[title](const QString &text) { title->setText(" " + text); });
// title
QLabel *title = new QLabel(" Chatterino");
QObject::connect(this, &QWidget::windowTitleChanged,
[title](const QString &text) { title->setText(" " + text); });
QSizePolicy policy(QSizePolicy::Ignored, QSizePolicy::Preferred);
policy.setHorizontalStretch(1);
// title->setBaseSize(0, 0);
title->setScaledContents(true);
title->setSizePolicy(policy);
buttonLayout->addWidget(title);
this->ui.titleLabel = title;
QSizePolicy policy(QSizePolicy::Ignored, QSizePolicy::Preferred);
policy.setHorizontalStretch(1);
// title->setBaseSize(0, 0);
title->setScaledContents(true);
title->setSizePolicy(policy);
buttonLayout->addWidget(title);
this->ui.titleLabel = title;
// buttons
TitleBarButton *_minButton = new TitleBarButton;
_minButton->setButtonStyle(TitleBarButton::Minimize);
TitleBarButton *_maxButton = new TitleBarButton;
_maxButton->setButtonStyle(TitleBarButton::Maximize);
TitleBarButton *_exitButton = new TitleBarButton;
_exitButton->setButtonStyle(TitleBarButton::Close);
// buttons
TitleBarButton *_minButton = new TitleBarButton;
_minButton->setButtonStyle(TitleBarButton::Minimize);
TitleBarButton *_maxButton = new TitleBarButton;
_maxButton->setButtonStyle(TitleBarButton::Maximize);
TitleBarButton *_exitButton = new TitleBarButton;
_exitButton->setButtonStyle(TitleBarButton::Close);
QObject::connect(_minButton, &TitleBarButton::clicked, this, [this] {
this->setWindowState(Qt::WindowMinimized | this->windowState());
});
QObject::connect(_maxButton, &TitleBarButton::clicked, this, [this] {
this->setWindowState(this->windowState() == Qt::WindowMaximized
? Qt::WindowActive
: Qt::WindowMaximized);
});
QObject::connect(_exitButton, &TitleBarButton::clicked, this,
[this] { this->close(); });
QObject::connect(_minButton, &TitleBarButton::clicked, this, [this] {
this->setWindowState(Qt::WindowMinimized | this->windowState());
});
QObject::connect(_maxButton, &TitleBarButton::clicked, this, [this] {
this->setWindowState(this->windowState() == Qt::WindowMaximized
? Qt::WindowActive
: Qt::WindowMaximized);
});
QObject::connect(_exitButton, &TitleBarButton::clicked, this,
[this] { this->close(); });
this->ui.minButton = _minButton;
this->ui.maxButton = _maxButton;
this->ui.exitButton = _exitButton;
this->ui.minButton = _minButton;
this->ui.maxButton = _maxButton;
this->ui.exitButton = _exitButton;
this->ui.buttons.push_back(_minButton);
this->ui.buttons.push_back(_maxButton);
this->ui.buttons.push_back(_exitButton);
this->ui.buttons.push_back(_minButton);
this->ui.buttons.push_back(_maxButton);
this->ui.buttons.push_back(_exitButton);
// buttonLayout->addStretch(1);
buttonLayout->addWidget(_minButton);
buttonLayout->addWidget(_maxButton);
buttonLayout->addWidget(_exitButton);
buttonLayout->setSpacing(0);
// buttonLayout->addStretch(1);
buttonLayout->addWidget(_minButton);
buttonLayout->addWidget(_maxButton);
buttonLayout->addWidget(_exitButton);
buttonLayout->setSpacing(0);
}
}
this->ui.layoutBase = new BaseWidget(this);
layout->addWidget(this->ui.layoutBase);
@@ -115,9 +128,21 @@ void BaseWindow::init()
}
#endif
if (app->settings->windowTopMost.getValue()) {
this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint);
#ifdef USEWINSDK
// fourtf: don't ask me why we need to delay this
if (!(this->flags & Flags::TopMost)) {
QTimer::singleShot(1, this, [this] {
getApp()->settings->windowTopMost.connect([this](bool topMost, auto) {
::SetWindowPos(HWND(this->winId()), topMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0,
0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
});
});
}
#else
// if (getApp()->settings->windowTopMost.getValue()) {
// this->setWindowFlag(Qt::WindowStaysOnTopHint);
// }
#endif
}
void BaseWindow::setStayInScreenRect(bool value)
@@ -158,10 +183,12 @@ void BaseWindow::themeRefreshEvent()
palette.setColor(QPalette::Foreground, this->themeManager->window.text);
this->setPalette(palette);
QPalette palette_title;
palette_title.setColor(QPalette::Foreground,
this->themeManager->isLightTheme() ? "#333" : "#ccc");
this->ui.titleLabel->setPalette(palette_title);
if (this->ui.titleLabel) {
QPalette palette_title;
palette_title.setColor(QPalette::Foreground,
this->themeManager->isLightTheme() ? "#333" : "#ccc");
this->ui.titleLabel->setPalette(palette_title);
}
for (RippleEffectButton *button : this->ui.buttons) {
button->setMouseEffectColor(this->themeManager->window.text);
@@ -205,7 +232,7 @@ void BaseWindow::changeEvent(QEvent *)
TooltipWidget::getInstance()->hide();
#ifdef USEWINSDK
if (this->hasCustomWindowFrame()) {
if (this->ui.maxButton) {
this->ui.maxButton->setButtonStyle(this->windowState() & Qt::WindowMaximized
? TitleBarButton::Unmaximize
: TitleBarButton::Maximize);
@@ -222,10 +249,12 @@ void BaseWindow::leaveEvent(QEvent *)
TooltipWidget::getInstance()->hide();
}
void BaseWindow::moveTo(QWidget *parent, QPoint point)
void BaseWindow::moveTo(QWidget *parent, QPoint point, bool offset)
{
point.rx() += 16;
point.ry() += 16;
if (offset) {
point.rx() += 16;
point.ry() += 16;
}
this->move(point);
this->moveIntoDesktopRect(parent);
@@ -295,10 +324,18 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, long *r
NCCALCSIZE_PARAMS *ncp = (reinterpret_cast<NCCALCSIZE_PARAMS *>(msg->lParam));
ncp->lppos->flags |= SWP_NOREDRAW;
RECT *clientRect = &ncp->rgrc[0];
clientRect->left += cx;
clientRect->top += 0;
clientRect->right -= cx;
clientRect->bottom -= cy;
if (IsWindows10OrGreater()) {
clientRect->left += cx;
clientRect->top += 0;
clientRect->right -= cx;
clientRect->bottom -= cy;
} else {
clientRect->left += 1;
clientRect->top += 0;
clientRect->right -= 1;
clientRect->bottom -= 1;
}
}
*result = 0;
@@ -306,14 +343,13 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, long *r
} else {
return QWidget::nativeEvent(eventType, message, result);
}
break;
}
} break;
case WM_NCHITTEST: {
if (this->hasCustomWindowFrame()) {
*result = 0;
const LONG border_width = 8; // in pixels
RECT winrect;
GetWindowRect((HWND)winId(), &winrect);
GetWindowRect(HWND(winId()), &winrect);
long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam);
@@ -400,17 +436,18 @@ void BaseWindow::showEvent(QShowEvent *event)
{
if (!this->shown && this->isVisible() && this->hasCustomWindowFrame()) {
this->shown = true;
SetWindowLongPtr((HWND)this->winId(), GWL_STYLE,
WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX);
// SetWindowLongPtr((HWND)this->winId(), GWL_STYLE,
// WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_MAXIMIZEBOX |
// WS_MINIMIZEBOX);
const MARGINS shadow = {8, 8, 8, 8};
DwmExtendFrameIntoClientArea((HWND)this->winId(), &shadow);
DwmExtendFrameIntoClientArea(HWND(this->winId()), &shadow);
}
BaseWidget::showEvent(event);
}
void BaseWindow::paintEvent(QPaintEvent *event)
void BaseWindow::paintEvent(QPaintEvent *)
{
if (this->hasCustomWindowFrame()) {
QPainter painter(this);
@@ -434,13 +471,19 @@ void BaseWindow::calcButtonsSizes()
return;
}
if ((this->width() / this->getScale()) < 300) {
this->ui.minButton->setScaleIndependantSize(30, 30);
this->ui.maxButton->setScaleIndependantSize(30, 30);
this->ui.exitButton->setScaleIndependantSize(30, 30);
if (this->ui.minButton)
this->ui.minButton->setScaleIndependantSize(30, 30);
if (this->ui.maxButton)
this->ui.maxButton->setScaleIndependantSize(30, 30);
if (this->ui.exitButton)
this->ui.exitButton->setScaleIndependantSize(30, 30);
} else {
this->ui.minButton->setScaleIndependantSize(46, 30);
this->ui.maxButton->setScaleIndependantSize(46, 30);
this->ui.exitButton->setScaleIndependantSize(46, 30);
if (this->ui.minButton)
this->ui.minButton->setScaleIndependantSize(46, 30);
if (this->ui.maxButton)
this->ui.maxButton->setScaleIndependantSize(46, 30);
if (this->ui.exitButton)
this->ui.exitButton->setScaleIndependantSize(46, 30);
}
}
} // namespace widgets
+11 -5
View File
@@ -19,7 +19,9 @@ class BaseWindow : public BaseWidget
Q_OBJECT
public:
explicit BaseWindow(QWidget *parent = nullptr, bool enableCustomFrame = false);
enum Flags { None = 0, EnableCustomFrame = 1, FrameLess = 2, TopMost = 4 };
explicit BaseWindow(QWidget *parent = nullptr, Flags flags = None);
QWidget *getLayoutContainer();
bool hasCustomWindowFrame();
@@ -29,7 +31,9 @@ public:
void setStayInScreenRect(bool value);
bool getStayInScreenRect() const;
void moveTo(QWidget *widget, QPoint point);
void moveTo(QWidget *widget, QPoint point, bool offset = true);
Flags getFlags();
protected:
#ifdef USEWINSDK
@@ -51,16 +55,18 @@ private:
void calcButtonsSizes();
bool enableCustomFrame;
bool frameless;
bool stayInScreenRect = false;
bool shown = false;
Flags flags;
struct {
QHBoxLayout *titlebarBox;
QWidget *titleLabel;
QHBoxLayout *titlebarBox = nullptr;
QWidget *titleLabel = nullptr;
TitleBarButton *minButton = nullptr;
TitleBarButton *maxButton = nullptr;
TitleBarButton *exitButton = nullptr;
QWidget *layoutBase;
QWidget *layoutBase = nullptr;
std::vector<RippleEffectButton *> buttons;
} ui;
};
+35 -8
View File
@@ -1,12 +1,13 @@
#include "emotepopup.hpp"
#include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "messages/messagebuilder.hpp"
#include "providers/twitch/twitchchannel.hpp"
#include "singletons/accountmanager.hpp"
#include "widgets/notebook.hpp"
#include <QHBoxLayout>
#include <QShortcut>
#include <QTabWidget>
using namespace chatterino::providers::twitch;
@@ -16,14 +17,14 @@ namespace chatterino {
namespace widgets {
EmotePopup::EmotePopup()
: BaseWindow(nullptr, true)
: BaseWindow(nullptr, BaseWindow::EnableCustomFrame)
{
this->viewEmotes = new ChannelView();
this->viewEmojis = new ChannelView();
this->viewEmotes->setOverrideFlags((MessageElement::Flags)(
this->viewEmotes->setOverrideFlags(MessageElement::Flags(
MessageElement::Default | MessageElement::AlwaysShow | MessageElement::EmoteImages));
this->viewEmojis->setOverrideFlags((MessageElement::Flags)(
this->viewEmojis->setOverrideFlags(MessageElement::Flags(
MessageElement::Default | MessageElement::AlwaysShow | MessageElement::EmoteImages));
this->viewEmotes->setEnableScrollingToBottom(false);
@@ -32,7 +33,7 @@ EmotePopup::EmotePopup()
auto *layout = new QVBoxLayout(this);
this->getLayoutContainer()->setLayout(layout);
Notebook2 *notebook = new Notebook2(this);
Notebook *notebook = new Notebook(this);
layout->addWidget(notebook);
layout->setMargin(0);
@@ -83,10 +84,36 @@ void EmotePopup::loadChannel(ChannelPtr _channel)
auto app = getApp();
QString userID = app->accounts->Twitch.getCurrent()->getUserId();
QString userID = app->accounts->twitch.getCurrent()->getUserId();
// fourtf: the entire emote manager needs to be refactored so there's no point in trying to
// fix this pile of garbage
for (const auto &set : app->emotes->twitchAccountEmotes[userID.toStdString()].emoteSets) {
// TITLE
messages::MessageBuilder builder1;
builder1.append(new TextElement("Twitch Account Emotes", MessageElement::Text));
builder1.getMessage()->flags |= Message::Centered;
emoteChannel->addMessage(builder1.getMessage());
// EMOTES
messages::MessageBuilder builder2;
builder2.getMessage()->flags |= Message::Centered;
builder2.getMessage()->flags |= Message::DisableCompactEmotes;
for (const auto &emote : set.second) {
[&](const QString &key, const util::EmoteData &value) {
builder2.append((new EmoteElement(value, MessageElement::Flags::AlwaysShow))
->setLink(Link(Link::InsertText, key)));
}(QString::fromStdString(emote.code),
app->emotes->getTwitchEmoteById(QString::fromStdString(emote.id).toLong(),
QString::fromStdString(emote.code)));
}
emoteChannel->addMessage(builder2.getMessage());
}
addEmotes(app->emotes->twitchAccountEmotes[userID.toStdString()].emotes,
"Twitch Account Emotes", "Twitch Account Emote");
addEmotes(app->emotes->bttvGlobalEmotes, "BetterTTV Global Emotes", "BetterTTV Global Emote");
addEmotes(*channel->bttvChannelEmotes.get(), "BetterTTV Channel Emotes",
"BetterTTV Channel Emote");

Some files were not shown because too many files have changed in this diff Show More